예제 #1
0
        public void should_match_tracks(string file)
        {
            var path     = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Identification", file);
            var testcase = JsonConvert.DeserializeObject <IdTestCase>(File.ReadAllText(path));

            var artists         = GivenArtists(testcase.LibraryAuthors);
            var specifiedArtist = artists.SingleOrDefault(x => x.Metadata.Value.ForeignAuthorId == testcase.Author);
            var idOverrides     = new IdentificationOverrides {
                Author = specifiedArtist
            };

            var tracks = testcase.Tracks.Select(x => new LocalBook
            {
                Path          = x.Path.AsOsAgnostic(),
                FileTrackInfo = x.FileTrackInfo
            }).ToList();

            if (testcase.Fingerprints != null)
            {
                GivenFingerprints(testcase.Fingerprints);
            }

            var config = new ImportDecisionMakerConfig
            {
                NewDownload     = testcase.NewDownload,
                SingleRelease   = testcase.SingleRelease,
                IncludeExisting = false
            };

            var result = _Subject.Identify(tracks, idOverrides, config);

            result.Should().HaveCount(testcase.ExpectedMusicBrainzReleaseIds.Count);
        }
예제 #2
0
        public List <LocalEdition> Identify(List <LocalBook> localTracks, IdentificationOverrides idOverrides, ImportDecisionMakerConfig config)
        {
            // 1 group localTracks so that we think they represent a single release
            // 2 get candidates given specified author, book and release.  Candidates can include extra files already on disk.
            // 3 find best candidate
            var watch = System.Diagnostics.Stopwatch.StartNew();

            _logger.Debug("Starting track identification");

            var releases = GetLocalBookReleases(localTracks, config.SingleRelease);

            int i = 0;

            foreach (var localRelease in releases)
            {
                i++;
                _logger.ProgressInfo($"Identifying book {i}/{releases.Count}");
                IdentifyRelease(localRelease, idOverrides, config);
            }

            watch.Stop();

            _logger.Debug($"Track identification for {localTracks.Count} tracks took {watch.ElapsedMilliseconds}ms");

            return(releases);
        }
예제 #3
0
        private List <ManualImportItem> ProcessFolder(string folder, string downloadId, Author author, FilterFilesType filter, bool replaceExistingFiles)
        {
            DownloadClientItem downloadClientItem = null;
            var directoryInfo = new DirectoryInfo(folder);

            author = author ?? _parsingService.GetAuthor(directoryInfo.Name);

            if (downloadId.IsNotNullOrWhiteSpace())
            {
                var trackedDownload = _trackedDownloadService.Find(downloadId);
                downloadClientItem = trackedDownload?.DownloadItem;

                if (author == null)
                {
                    author = trackedDownload?.RemoteBook?.Author;
                }
            }

            var authorFiles = _diskScanService.GetBookFiles(folder).ToList();
            var idOverrides = new IdentificationOverrides
            {
                Author = author
            };
            var itemInfo = new ImportDecisionMakerInfo
            {
                DownloadClientItem = downloadClientItem,
                ParsedTrackInfo    = Parser.Parser.ParseTitle(directoryInfo.Name)
            };
            var config = new ImportDecisionMakerConfig
            {
                Filter          = filter,
                NewDownload     = true,
                SingleRelease   = false,
                IncludeExisting = !replaceExistingFiles,
                AddNewAuthors   = false,
                KeepAllEditions = true
            };

            var decisions = _importDecisionMaker.GetImportDecisions(authorFiles, idOverrides, itemInfo, config);

            // paths will be different for new and old files which is why we need to map separately
            var newFiles = authorFiles.Join(decisions,
                                            f => f.FullName,
                                            d => d.Item.Path,
                                            (f, d) => new { File = f, Decision = d },
                                            PathEqualityComparer.Instance);

            var newItems          = newFiles.Select(x => MapItem(x.Decision, downloadId, replaceExistingFiles, false));
            var existingDecisions = decisions.Except(newFiles.Select(x => x.Decision));
            var existingItems     = existingDecisions.Select(x => MapItem(x, null, replaceExistingFiles, false));

            return(newItems.Concat(existingItems).ToList());
        }
        private List <ImportResult> ProcessFile(IFileInfo fileInfo, ImportMode importMode, Author author, DownloadClientItem downloadClientItem)
        {
            if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._"))
            {
                _logger.Debug("[{0}] starts with '._', skipping", fileInfo.FullName);

                return(new List <ImportResult>
                {
                    new ImportResult(new ImportDecision <LocalBook>(new LocalBook {
                        Path = fileInfo.FullName
                    }, new Rejection("Invalid music file, filename starts with '._'")), "Invalid music file, filename starts with '._'")
                });
            }

            if (downloadClientItem == null)
            {
                if (_diskProvider.IsFileLocked(fileInfo.FullName))
                {
                    return(new List <ImportResult>
                    {
                        FileIsLockedResult(fileInfo.FullName)
                    });
                }
            }

            var idOverrides = new IdentificationOverrides
            {
                Author = author
            };
            var idInfo = new ImportDecisionMakerInfo
            {
                DownloadClientItem = downloadClientItem
            };
            var idConfig = new ImportDecisionMakerConfig
            {
                Filter          = FilterFilesType.None,
                NewDownload     = true,
                SingleRelease   = false,
                IncludeExisting = false,
                AddNewAuthors   = false
            };

            var decisions = _importDecisionMaker.GetImportDecisions(new List <IFileInfo>()
            {
                fileInfo
            }, idOverrides, idInfo, idConfig);

            return(_importApprovedTracks.Import(decisions, true, downloadClientItem, importMode));
        }
예제 #5
0
        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)
            });
        }
예제 #6
0
        private bool SatisfiesOverride(Edition edition, IdentificationOverrides idOverride)
        {
            if (idOverride?.Edition != null)
            {
                return(edition.ForeignEditionId == idOverride.Edition.ForeignEditionId);
            }

            if (idOverride?.Book != null)
            {
                return(edition.Book.Value.ForeignBookId == idOverride.Book.ForeignBookId);
            }

            if (idOverride?.Author != null)
            {
                return(edition.Book.Value.Author.Value.ForeignAuthorId == idOverride.Author.ForeignAuthorId);
            }

            return(true);
        }
예제 #7
0
        private void IdentifyRelease(LocalEdition localBookRelease, IdentificationOverrides idOverrides, ImportDecisionMakerConfig config)
        {
            var watch = System.Diagnostics.Stopwatch.StartNew();

            var candidateReleases = _candidateService.GetDbCandidatesFromTags(localBookRelease, idOverrides, config.IncludeExisting);

            if (candidateReleases.Count == 0 && config.AddNewAuthors)
            {
                candidateReleases = _candidateService.GetRemoteCandidates(localBookRelease);
            }

            if (candidateReleases.Count == 0)
            {
                // can't find any candidates even after fingerprinting
                // populate the overrides and return
                foreach (var localTrack in localBookRelease.LocalBooks)
                {
                    localTrack.Edition = idOverrides.Edition;
                    localTrack.Book    = idOverrides.Book;
                    localTrack.Author  = idOverrides.Author;
                }

                return;
            }

            _logger.Debug($"Got {candidateReleases.Count} candidates for {localBookRelease.LocalBooks.Count} tracks in {watch.ElapsedMilliseconds}ms");

            // convert all the TrackFiles that represent extra files to List<LocalTrack>
            var allLocalTracks = ToLocalTrack(candidateReleases
                                              .SelectMany(x => x.ExistingFiles)
                                              .DistinctBy(x => x.Path), localBookRelease);

            _logger.Debug($"Retrieved {allLocalTracks.Count} possible tracks in {watch.ElapsedMilliseconds}ms");

            GetBestRelease(localBookRelease, candidateReleases, allLocalTracks);

            _logger.Debug($"Best release found in {watch.ElapsedMilliseconds}ms");

            localBookRelease.PopulateMatch();

            _logger.Debug($"IdentifyRelease done in {watch.ElapsedMilliseconds}ms");
        }
예제 #8
0
        private void IdentifyRelease(LocalEdition localBookRelease, IdentificationOverrides idOverrides, ImportDecisionMakerConfig config)
        {
            var  watch      = System.Diagnostics.Stopwatch.StartNew();
            bool usedRemote = false;

            IEnumerable <CandidateEdition> candidateReleases = _candidateService.GetDbCandidatesFromTags(localBookRelease, idOverrides, config.IncludeExisting);

            // convert all the TrackFiles that represent extra files to List<LocalTrack>
            // local candidates are actually a list so this is fine to enumerate
            var allLocalTracks = ToLocalTrack(candidateReleases
                                              .SelectMany(x => x.ExistingFiles)
                                              .DistinctBy(x => x.Path), localBookRelease);

            _logger.Debug($"Retrieved {allLocalTracks.Count} possible tracks in {watch.ElapsedMilliseconds}ms");

            if (!candidateReleases.Any())
            {
                candidateReleases = _candidateService.GetRemoteCandidates(localBookRelease, idOverrides);
                if (!config.AddNewAuthors)
                {
                    candidateReleases = candidateReleases.Where(x => x.Edition.Book.Value.Id > 0);
                }

                usedRemote = true;
            }

            if (!candidateReleases.Any())
            {
                // can't find any candidates even after using remote search
                // populate the overrides and return
                foreach (var localTrack in localBookRelease.LocalBooks)
                {
                    localTrack.Edition = idOverrides.Edition;
                    localTrack.Book    = idOverrides.Book;
                    localTrack.Author  = idOverrides.Author;
                }

                return;
            }

            GetBestRelease(localBookRelease, candidateReleases, allLocalTracks);

            // If the result isn't great and we haven't tried remote candidates, try looking for remote candidates
            // Goodreads may have a better edition of a local book
            if (localBookRelease.Distance.NormalizedDistance() > 0.15 && !usedRemote)
            {
                _logger.Debug("Match not good enough, trying remote candidates");
                candidateReleases = _candidateService.GetRemoteCandidates(localBookRelease, idOverrides);

                if (!config.AddNewAuthors)
                {
                    candidateReleases = candidateReleases.Where(x => x.Edition.Book.Value.Id > 0);
                }

                GetBestRelease(localBookRelease, candidateReleases, allLocalTracks);
            }

            _logger.Debug($"Best release found in {watch.ElapsedMilliseconds}ms");

            localBookRelease.PopulateMatch();

            _logger.Debug($"IdentifyRelease done in {watch.ElapsedMilliseconds}ms");
        }
예제 #9
0
        public List <ManualImportItem> UpdateItems(List <ManualImportItem> items)
        {
            var replaceExistingFiles = items.All(x => x.ReplaceExistingFiles);
            var groupedItems         = items.Where(x => !x.AdditionalFile).GroupBy(x => x.Album?.Id);

            _logger.Debug($"UpdateItems, {groupedItems.Count()} groups, replaceExisting {replaceExistingFiles}");

            var result = new List <ManualImportItem>();

            foreach (var group in groupedItems)
            {
                _logger.Debug("UpdateItems, group key: {0}", group.Key);

                var disableReleaseSwitching = group.First().DisableReleaseSwitching;

                var files      = group.Select(x => _diskProvider.GetFileInfo(x.Path)).ToList();
                var idOverride = new IdentificationOverrides
                {
                    Artist       = group.First().Artist,
                    Album        = group.First().Album,
                    AlbumRelease = group.First().Release
                };
                var config = new ImportDecisionMakerConfig
                {
                    Filter          = FilterFilesType.None,
                    NewDownload     = true,
                    SingleRelease   = true,
                    IncludeExisting = !replaceExistingFiles,
                    AddNewArtists   = false
                };
                var decisions = _importDecisionMaker.GetImportDecisions(files, idOverride, null, config);

                var existingItems = group.Join(decisions,
                                               i => i.Path,
                                               d => d.Item.Path,
                                               (i, d) => new { Item = i, Decision = d },
                                               PathEqualityComparer.Instance);

                foreach (var pair in existingItems)
                {
                    var item     = pair.Item;
                    var decision = pair.Decision;

                    if (decision.Item.Artist != null)
                    {
                        item.Artist = decision.Item.Artist;
                    }

                    if (decision.Item.Album != null)
                    {
                        item.Album   = decision.Item.Album;
                        item.Release = decision.Item.Release;
                    }

                    if (decision.Item.Tracks.Any())
                    {
                        item.Tracks = decision.Item.Tracks;
                    }

                    item.Rejections = decision.Rejections;

                    result.Add(item);
                }

                var newDecisions = decisions.Except(existingItems.Select(x => x.Decision));
                result.AddRange(newDecisions.Select(x => MapItem(x, null, replaceExistingFiles, disableReleaseSwitching)));
            }

            return(result);
        }
예제 #10
0
        public List <CandidateEdition> GetDbCandidatesFromTags(LocalEdition localEdition, IdentificationOverrides idOverrides, bool includeExisting)
        {
            var watch = System.Diagnostics.Stopwatch.StartNew();

            // Generally author, book 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 <CandidateEdition> candidateReleases;

            // if we have a Book ID, use that
            Book tagMbidRelease = null;
            List <CandidateEdition> tagCandidate = null;

            // TODO: select by ISBN?
            // var releaseIds = localEdition.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 = GetDbCandidatesByRelease(new List<BookRelease> { tagMbidRelease }, includeExisting);
            //     }
            // }
            if (idOverrides?.Edition != null)
            {
                var release = idOverrides.Edition;
                _logger.Debug("Edition {0} was forced", release);
                candidateReleases = GetDbCandidatesByEdition(new List <Edition> {
                    release
                }, includeExisting);
            }
            else if (idOverrides?.Book != null)
            {
                // use the release from file tags if it exists and agrees with the specified book
                if (tagMbidRelease?.Id == idOverrides.Book.Id)
                {
                    candidateReleases = tagCandidate;
                }
                else
                {
                    candidateReleases = GetDbCandidatesByBook(idOverrides.Book, includeExisting);
                }
            }
            else if (idOverrides?.Author != null)
            {
                // use the release from file tags if it exists and agrees with the specified book
                if (tagMbidRelease?.AuthorMetadataId == idOverrides.Author.AuthorMetadataId)
                {
                    candidateReleases = tagCandidate;
                }
                else
                {
                    candidateReleases = GetDbCandidatesByAuthor(localEdition, idOverrides.Author, includeExisting);
                }
            }
            else
            {
                if (tagMbidRelease != null)
                {
                    candidateReleases = tagCandidate;
                }
                else
                {
                    candidateReleases = GetDbCandidates(localEdition, includeExisting);
                }
            }

            watch.Stop();
            _logger.Debug($"Getting {candidateReleases.Count} candidates from tags for {localEdition.LocalBooks.Count} tracks took {watch.ElapsedMilliseconds}ms");

            return(candidateReleases);
        }
예제 #11
0
        public List <CandidateAlbumRelease> GetDbCandidatesFromTags(LocalAlbumRelease localAlbumRelease, IdentificationOverrides idOverrides, 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 = GetDbCandidatesByRelease(new List <AlbumRelease> {
                        tagMbidRelease
                    }, includeExisting);
                }
            }

            if (idOverrides?.AlbumRelease != null)
            {
                // this case overrides the release picked up from the file tags
                var release = idOverrides.AlbumRelease;
                _logger.Debug("Release {0} [{1} tracks] was forced", release, release.TrackCount);
                candidateReleases = GetDbCandidatesByRelease(new List <AlbumRelease> {
                    release
                }, includeExisting);
            }
            else if (idOverrides?.Album != null)
            {
                // use the release from file tags if it exists and agrees with the specified album
                if (tagMbidRelease?.AlbumId == idOverrides.Album.Id)
                {
                    candidateReleases = tagCandidate;
                }
                else
                {
                    candidateReleases = GetDbCandidatesByAlbum(localAlbumRelease, idOverrides.Album, includeExisting);
                }
            }
            else if (idOverrides?.Artist != null)
            {
                // use the release from file tags if it exists and agrees with the specified album
                if (tagMbidRelease?.Album.Value.ArtistMetadataId == idOverrides.Artist.ArtistMetadataId)
                {
                    candidateReleases = tagCandidate;
                }
                else
                {
                    candidateReleases = GetDbCandidatesByArtist(localAlbumRelease, idOverrides.Artist, includeExisting);
                }
            }
            else
            {
                if (tagMbidRelease != null)
                {
                    candidateReleases = tagCandidate;
                }
                else
                {
                    candidateReleases = GetDbCandidates(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);
        }
예제 #12
0
        public IEnumerable <CandidateEdition> GetRemoteCandidates(LocalEdition localEdition, IdentificationOverrides idOverrides)
        {
            // TODO handle edition override

            // Gets candidate book releases from the metadata server.
            // Will eventually need adding locally if we find a match
            List <Book> remoteBooks;
            var         seenCandidates = new HashSet <string>();

            var isbns     = localEdition.LocalBooks.Select(x => x.FileTrackInfo.Isbn).Distinct().ToList();
            var asins     = localEdition.LocalBooks.Select(x => x.FileTrackInfo.Asin).Distinct().ToList();
            var goodreads = localEdition.LocalBooks.Select(x => x.FileTrackInfo.GoodreadsId).Distinct().ToList();

            // grab possibilities for all the IDs present
            if (isbns.Count == 1 && isbns[0].IsNotNullOrWhiteSpace())
            {
                _logger.Trace($"Searching by isbn {isbns[0]}");

                try
                {
                    remoteBooks = _bookSearchService.SearchByIsbn(isbns[0]);
                }
                catch (GoodreadsException e)
                {
                    _logger.Info(e, "Skipping ISBN search due to Goodreads Error");
                    remoteBooks = new List <Book>();
                }

                foreach (var candidate in ToCandidates(remoteBooks, seenCandidates, idOverrides))
                {
                    yield return(candidate);
                }
            }

            if (asins.Count == 1 &&
                asins[0].IsNotNullOrWhiteSpace() &&
                asins[0].Length == 10)
            {
                _logger.Trace($"Searching by asin {asins[0]}");

                try
                {
                    remoteBooks = _bookSearchService.SearchByAsin(asins[0]);
                }
                catch (GoodreadsException e)
                {
                    _logger.Info(e, "Skipping ASIN search due to Goodreads Error");
                    remoteBooks = new List <Book>();
                }

                foreach (var candidate in ToCandidates(remoteBooks, seenCandidates, idOverrides))
                {
                    yield return(candidate);
                }
            }

            if (goodreads.Count == 1 &&
                goodreads[0].IsNotNullOrWhiteSpace())
            {
                if (int.TryParse(goodreads[0], out var id))
                {
                    _logger.Trace($"Searching by goodreads id {id}");

                    try
                    {
                        remoteBooks = _bookSearchService.SearchByGoodreadsId(id);
                    }
                    catch (GoodreadsException e)
                    {
                        _logger.Info(e, "Skipping Goodreads ID search due to Goodreads Error");
                        remoteBooks = new List <Book>();
                    }

                    foreach (var candidate in ToCandidates(remoteBooks, seenCandidates, idOverrides))
                    {
                        yield return(candidate);
                    }
                }
            }

            // If we got an id result, or any overrides are set, stop
            if (seenCandidates.Any() ||
                idOverrides?.Edition != null ||
                idOverrides?.Book != null ||
                idOverrides?.Author != null)
            {
                yield break;
            }

            // fall back to author / book name search
            var authorTags = new List <string>();

            if (TrackGroupingService.IsVariousAuthors(localEdition.LocalBooks))
            {
                authorTags.Add("Various Authors");
            }
            else
            {
                authorTags.AddRange(localEdition.LocalBooks.MostCommon(x => x.FileTrackInfo.Authors));
            }

            var bookTag = localEdition.LocalBooks.MostCommon(x => x.FileTrackInfo.BookTitle) ?? "";

            // If no valid author or book tags, stop
            if (!authorTags.Any() || bookTag.IsNullOrWhiteSpace())
            {
                yield break;
            }

            // Search by author+book
            foreach (var authorTag in authorTags)
            {
                try
                {
                    remoteBooks = _bookSearchService.SearchForNewBook(bookTag, authorTag);
                }
                catch (GoodreadsException e)
                {
                    _logger.Info(e, "Skipping author/title search due to Goodreads Error");
                    remoteBooks = new List <Book>();
                }

                foreach (var candidate in ToCandidates(remoteBooks, seenCandidates, idOverrides))
                {
                    yield return(candidate);
                }
            }

            // If we got an author/book search result, stop
            if (seenCandidates.Any())
            {
                yield break;
            }

            // Search by just book title
            try
            {
                remoteBooks = _bookSearchService.SearchForNewBook(bookTag, null);
            }
            catch (GoodreadsException e)
            {
                _logger.Info(e, "Skipping book title search due to Goodreads Error");
                remoteBooks = new List <Book>();
            }

            foreach (var candidate in ToCandidates(remoteBooks, seenCandidates, idOverrides))
            {
                yield return(candidate);
            }

            // Search by just author
            foreach (var a in authorTags)
            {
                try
                {
                    remoteBooks = _bookSearchService.SearchForNewBook(a, null);
                }
                catch (GoodreadsException e)
                {
                    _logger.Info(e, "Skipping author search due to Goodreads Error");
                    remoteBooks = new List <Book>();
                }

                foreach (var candidate in ToCandidates(remoteBooks, seenCandidates, idOverrides))
                {
                    yield return(candidate);
                }
            }
        }
예제 #13
0
        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");
        }
예제 #14
0
        public void Setup()
        {
            _albumpass1 = new Mock <IImportDecisionEngineSpecification <LocalEdition> >();
            _albumpass2 = new Mock <IImportDecisionEngineSpecification <LocalEdition> >();
            _albumpass3 = new Mock <IImportDecisionEngineSpecification <LocalEdition> >();

            _albumfail1 = new Mock <IImportDecisionEngineSpecification <LocalEdition> >();
            _albumfail2 = new Mock <IImportDecisionEngineSpecification <LocalEdition> >();
            _albumfail3 = new Mock <IImportDecisionEngineSpecification <LocalEdition> >();

            _pass1 = new Mock <IImportDecisionEngineSpecification <LocalBook> >();
            _pass2 = new Mock <IImportDecisionEngineSpecification <LocalBook> >();
            _pass3 = new Mock <IImportDecisionEngineSpecification <LocalBook> >();

            _fail1 = new Mock <IImportDecisionEngineSpecification <LocalBook> >();
            _fail2 = new Mock <IImportDecisionEngineSpecification <LocalBook> >();
            _fail3 = new Mock <IImportDecisionEngineSpecification <LocalBook> >();

            _albumpass1.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalEdition>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Accept());
            _albumpass2.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalEdition>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Accept());
            _albumpass3.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalEdition>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Accept());

            _albumfail1.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalEdition>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Reject("_albumfail1"));
            _albumfail2.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalEdition>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Reject("_albumfail2"));
            _albumfail3.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalEdition>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Reject("_albumfail3"));

            _pass1.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalBook>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Accept());
            _pass2.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalBook>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Accept());
            _pass3.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalBook>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Accept());

            _fail1.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalBook>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Reject("_fail1"));
            _fail2.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalBook>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Reject("_fail2"));
            _fail3.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalBook>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Reject("_fail3"));

            _artist = Builder <Author> .CreateNew()
                      .With(e => e.QualityProfileId = 1)
                      .With(e => e.QualityProfile   = new QualityProfile {
                Items = Qualities.QualityFixture.GetDefaultQualities()
            })
                      .Build();

            _album = Builder <Book> .CreateNew()
                     .With(x => x.Author = _artist)
                     .Build();

            _edition = Builder <Edition> .CreateNew()
                       .With(x => x.Book = _album)
                       .Build();

            _quality = new QualityModel(Quality.MP3_320);

            _localTrack = new LocalBook
            {
                Author  = _artist,
                Quality = _quality,
                Book    = new Book(),
                Path    = @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi".AsOsAgnostic()
            };

            _idOverrides = new IdentificationOverrides
            {
                Author = _artist
            };

            _idConfig = new ImportDecisionMakerConfig();

            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 <LocalBook> >(), It.IsAny <IdentificationOverrides>(), It.IsAny <ImportDecisionMakerConfig>()))
            .Returns((List <LocalBook> tracks, IdentificationOverrides idOverrides, ImportDecisionMakerConfig config) =>
            {
                var ret     = new LocalEdition(tracks);
                ret.Edition = _edition;
                return(new List <LocalEdition> {
                    ret
                });
            });

            Mocker.GetMock <IMediaFileService>()
            .Setup(c => c.FilterUnchangedFiles(It.IsAny <List <IFileInfo> >(), It.IsAny <FilterFilesType>()))
            .Returns((List <IFileInfo> files, FilterFilesType filter) => files);

            GivenSpecifications(_albumpass1);
        }
        private List <ImportResult> ProcessFolder(IDirectoryInfo directoryInfo, ImportMode importMode, Author author, DownloadClientItem downloadClientItem)
        {
            if (_authorService.AuthorPathExists(directoryInfo.FullName))
            {
                _logger.Warn("Unable to process folder that is mapped to an existing author");
                return(new List <ImportResult>());
            }

            var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
            var folderInfo    = Parser.Parser.ParseBookTitle(directoryInfo.Name);
            var trackInfo     = new ParsedTrackInfo {
            };

            if (folderInfo != null)
            {
                _logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality);

                trackInfo = new ParsedTrackInfo
                {
                    AlbumTitle   = folderInfo.BookTitle,
                    ArtistTitle  = folderInfo.AuthorName,
                    Quality      = folderInfo.Quality,
                    ReleaseGroup = folderInfo.ReleaseGroup,
                    ReleaseHash  = folderInfo.ReleaseHash,
                };
            }
            else
            {
                trackInfo = null;
            }

            var audioFiles = _diskScanService.FilterFiles(directoryInfo.FullName, _diskScanService.GetBookFiles(directoryInfo.FullName));

            if (downloadClientItem == null)
            {
                foreach (var audioFile in audioFiles)
                {
                    if (_diskProvider.IsFileLocked(audioFile.FullName))
                    {
                        return(new List <ImportResult>
                        {
                            FileIsLockedResult(audioFile.FullName)
                        });
                    }
                }
            }

            var idOverrides = new IdentificationOverrides
            {
                Author = author
            };
            var idInfo = new ImportDecisionMakerInfo
            {
                DownloadClientItem = downloadClientItem,
                ParsedTrackInfo    = trackInfo
            };
            var idConfig = new ImportDecisionMakerConfig
            {
                Filter          = FilterFilesType.None,
                NewDownload     = true,
                SingleRelease   = false,
                IncludeExisting = false,
                AddNewAuthors   = false
            };

            var decisions     = _importDecisionMaker.GetImportDecisions(audioFiles, idOverrides, idInfo, idConfig);
            var importResults = _importApprovedTracks.Import(decisions, true, downloadClientItem, importMode);

            if (importMode == ImportMode.Auto)
            {
                importMode = (downloadClientItem == null || downloadClientItem.CanMoveFiles) ? ImportMode.Move : ImportMode.Copy;
            }

            if (importMode == ImportMode.Move &&
                importResults.Any(i => i.Result == ImportResultType.Imported) &&
                ShouldDeleteFolder(directoryInfo, author))
            {
                _logger.Debug("Deleting folder after importing valid files");
                _diskProvider.DeleteFolder(directoryInfo.FullName, true);
            }

            return(importResults);
        }
예제 #16
0
        private List <CandidateEdition> ToCandidates(IEnumerable <Book> books, HashSet <string> seenCandidates, IdentificationOverrides idOverrides)
        {
            var candidates = new List <CandidateEdition>();

            foreach (var book in books)
            {
                // We have to make sure various bits and pieces are populated that are normally handled
                // by a database lazy load
                foreach (var edition in book.Editions.Value)
                {
                    edition.Book = book;

                    if (!seenCandidates.Contains(edition.ForeignEditionId) && SatisfiesOverride(edition, idOverrides))
                    {
                        seenCandidates.Add(edition.ForeignEditionId);
                        candidates.Add(new CandidateEdition
                        {
                            Edition       = edition,
                            ExistingFiles = new List <BookFile>()
                        });
                    }
                }
            }

            return(candidates);
        }
예제 #17
0
        public List <CandidateAlbumRelease> GetDbCandidatesFromFingerprint(LocalAlbumRelease localAlbumRelease, IdentificationOverrides idOverrides, 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 (idOverrides?.AlbumRelease != null)
            {
                allReleases = allReleases.Where(x => x.Id == idOverrides.AlbumRelease.Id).ToList();
            }
            else if (idOverrides?.Album != null)
            {
                allReleases = allReleases.Where(x => x.AlbumId == idOverrides.Album.Id).ToList();
            }
            else if (idOverrides?.Artist != null)
            {
                allReleases = allReleases.Where(x => x.Album.Value.ArtistMetadataId == idOverrides.Artist.ArtistMetadataId).ToList();
            }

            return(GetDbCandidatesByRelease(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));
        }