private ImportDecision <LocalEdition> GetDecision(LocalEdition localEdition, DownloadClientItem downloadClientItem) { ImportDecision <LocalEdition> decision = null; if (localEdition.Edition == null) { decision = new ImportDecision <LocalEdition>(localEdition, new Rejection($"Couldn't find similar book for {localEdition}")); } else { var reasons = _bookSpecifications.Select(c => EvaluateSpec(c, localEdition, downloadClientItem)) .Where(c => c != null); decision = new ImportDecision <LocalEdition>(localEdition, reasons.ToArray()); } if (decision == null) { _logger.Error("Unable to make a decision on {0}", localEdition); } else if (decision.Rejections.Any()) { _logger.Debug("Book rejected for the following reasons: {0}", string.Join(", ", decision.Rejections)); } else { _logger.Debug("Book accepted"); } return(decision); }
public void should_not_throw_on_goodreads_exception() { Mocker.GetMock <ISearchForNewBook>() .Setup(s => s.SearchForNewBook(It.IsAny <string>(), It.IsAny <string>())) .Throws(new GoodreadsException("Bad search")); var edition = new LocalEdition { LocalBooks = new List <LocalBook> { new LocalBook { FileTrackInfo = new ParsedTrackInfo { Authors = new List <string> { "Author" }, BookTitle = "Book" } } } }; Subject.GetRemoteCandidates(edition, null).Should().BeEmpty(); }
private List <CandidateEdition> GetDbCandidates(LocalEdition localEdition, bool includeExisting) { // most general version, nothing has been specified. // get all plausible authors, then all plausible books, then get releases for each of these. var candidateReleases = new List <CandidateEdition>(); // check if it looks like VA. if (TrackGroupingService.IsVariousAuthors(localEdition.LocalBooks)) { var va = _authorService.FindById(DistanceCalculator.VariousAuthorIds[0]); if (va != null) { candidateReleases.AddRange(GetDbCandidatesByAuthor(localEdition, va, includeExisting)); } } var authorTags = localEdition.LocalBooks.MostCommon(x => x.FileTrackInfo.Authors) ?? new List <string>(); if (authorTags.Any()) { foreach (var authorTag in authorTags) { var possibleAuthors = _authorService.GetCandidates(authorTag); foreach (var author in possibleAuthors) { candidateReleases.AddRange(GetDbCandidatesByAuthor(localEdition, author, includeExisting)); } } } return(candidateReleases); }
private List <CandidateEdition> GetDbCandidates(LocalEdition localEdition, bool includeExisting) { _logger.Trace($"Getting candidates for {0}", localEdition); // 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 <CandidateEdition>(); // check if it looks like VA. if (TrackGroupingService.IsVariousArtists(localEdition.LocalBooks)) { var va = _authorService.FindById(DistanceCalculator.VariousAuthorIds[0]); if (va != null) { candidateReleases.AddRange(GetDbCandidatesByAuthor(localEdition, va, includeExisting)); } } var artistTag = localEdition.LocalBooks.MostCommon(x => x.FileTrackInfo.ArtistTitle) ?? ""; if (artistTag.IsNotNullOrWhiteSpace()) { var possibleArtists = _authorService.GetCandidates(artistTag); foreach (var author in possibleArtists) { candidateReleases.AddRange(GetDbCandidatesByAuthor(localEdition, author, includeExisting)); } } return(candidateReleases); }
private void EnsureData(LocalEdition edition) { if (edition.Edition != null && edition.Edition.Book.Value.Author.Value.QualityProfileId == 0) { var rootFolder = _rootFolderService.GetBestRootFolder(edition.LocalBooks.First().Path); var qualityProfile = _qualityProfileService.Get(rootFolder.DefaultQualityProfileId); var author = edition.Edition.Book.Value.Author.Value; author.QualityProfileId = qualityProfile.Id; author.QualityProfile = qualityProfile; } }
private void GetBestRelease(LocalEdition localBookRelease, IEnumerable <CandidateEdition> candidateReleases, List <LocalBook> extraTracksOnDisk, out bool seenCandidate) { var watch = System.Diagnostics.Stopwatch.StartNew(); _logger.Debug("Matching {0} track files against candidates", localBookRelease.TrackCount); _logger.Trace("Processing files:\n{0}", string.Join("\n", localBookRelease.LocalBooks.Select(x => x.Path))); var bestDistance = localBookRelease.Edition != null?localBookRelease.Distance.NormalizedDistance() : 1.0; seenCandidate = false; foreach (var candidateRelease in candidateReleases) { seenCandidate = true; var release = candidateRelease.Edition; _logger.Debug($"Trying Release {release}"); var rwatch = System.Diagnostics.Stopwatch.StartNew(); var extraTrackPaths = candidateRelease.ExistingFiles.Select(x => x.Path).ToList(); var extraTracks = extraTracksOnDisk.Where(x => extraTrackPaths.Contains(x.Path)).ToList(); var allLocalTracks = localBookRelease.LocalBooks.Concat(extraTracks).DistinctBy(x => x.Path).ToList(); var distance = DistanceCalculator.BookDistance(allLocalTracks, release); var currDistance = distance.NormalizedDistance(); rwatch.Stop(); _logger.Debug("Release {0} has distance {1} vs best distance {2} [{3}ms]", release, currDistance, bestDistance, rwatch.ElapsedMilliseconds); if (currDistance < bestDistance) { bestDistance = currDistance; localBookRelease.Distance = distance; localBookRelease.Edition = release; localBookRelease.ExistingTracks = extraTracks; if (currDistance == 0.0) { break; } } } watch.Stop(); _logger.Debug($"Best release: {localBookRelease.Edition} Distance {localBookRelease.Distance.NormalizedDistance()} found in {watch.ElapsedMilliseconds}ms"); }
public LocalEdition Augment(LocalEdition localAlbum) { foreach (var augmenter in _albumAugmenters) { try { augmenter.Aggregate(localAlbum, false); } catch (Exception ex) { _logger.Warn(ex, ex.Message); } } return(localAlbum); }
private List <CandidateEdition> GetDbCandidatesByAuthor(LocalEdition localEdition, Author author, bool includeExisting) { _logger.Trace("Getting candidates for {0}", author); var candidateReleases = new List <CandidateEdition>(); var bookTag = localEdition.LocalBooks.MostCommon(x => x.FileTrackInfo.BookTitle) ?? ""; if (bookTag.IsNotNullOrWhiteSpace()) { var possibleBooks = _bookService.GetCandidates(author.AuthorMetadataId, bookTag); foreach (var book in possibleBooks) { candidateReleases.AddRange(GetDbCandidatesByBook(book, includeExisting)); } } return(candidateReleases); }
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"); }
private List <LocalBook> ToLocalTrack(IEnumerable <BookFile> trackfiles, LocalEdition localRelease) { var scanned = trackfiles.Join(localRelease.LocalBooks, 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 LocalBook { 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); }
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"); }
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); }
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); } } }
public List <CandidateEdition> GetRemoteCandidates(LocalEdition localEdition) { // Gets candidate book releases from the metadata server. // Will eventually need adding locally if we find a match var watch = System.Diagnostics.Stopwatch.StartNew(); List <Book> remoteBooks = null; var candidates = new List <CandidateEdition>(); var goodreads = localEdition.LocalBooks.Select(x => x.FileTrackInfo.GoodreadsId).Distinct().ToList(); var isbns = localEdition.LocalBooks.Select(x => x.FileTrackInfo.Isbn).Distinct().ToList(); var asins = localEdition.LocalBooks.Select(x => x.FileTrackInfo.Asin).Distinct().ToList(); try { if (goodreads.Count == 1 && goodreads[0].IsNotNullOrWhiteSpace()) { if (int.TryParse(goodreads[0], out var id)) { _logger.Trace($"Searching by goodreads id {id}"); remoteBooks = _bookSearchService.SearchByGoodreadsId(id); } } if ((remoteBooks == null || !remoteBooks.Any()) && isbns.Count == 1 && isbns[0].IsNotNullOrWhiteSpace()) { _logger.Trace($"Searching by isbn {isbns[0]}"); remoteBooks = _bookSearchService.SearchByIsbn(isbns[0]); } // Calibre puts junk asins into books it creates so check for sensible length if ((remoteBooks == null || !remoteBooks.Any()) && asins.Count == 1 && asins[0].IsNotNullOrWhiteSpace() && asins[0].Length == 10) { _logger.Trace($"Searching by asin {asins[0]}"); remoteBooks = _bookSearchService.SearchByAsin(asins[0]); } // if no asin/isbn or no result, fall back to text search if (remoteBooks == null || !remoteBooks.Any()) { // fall back to author / book name search string artistTag; if (TrackGroupingService.IsVariousArtists(localEdition.LocalBooks)) { artistTag = "Various Artists"; } else { artistTag = localEdition.LocalBooks.MostCommon(x => x.FileTrackInfo.ArtistTitle) ?? ""; } var albumTag = localEdition.LocalBooks.MostCommon(x => x.FileTrackInfo.AlbumTitle) ?? ""; if (artistTag.IsNullOrWhiteSpace() || albumTag.IsNullOrWhiteSpace()) { return(candidates); } remoteBooks = _bookSearchService.SearchForNewBook(albumTag, artistTag); if (!remoteBooks.Any()) { var albumSearch = _bookSearchService.SearchForNewBook(albumTag, null); var artistSearch = _bookSearchService.SearchForNewBook(artistTag, null); remoteBooks = albumSearch.Concat(artistSearch).DistinctBy(x => x.ForeignBookId).ToList(); } } } catch (SkyHookException e) { _logger.Info(e, "Skipping book due to SkyHook error"); remoteBooks = new List <Book>(); } foreach (var book in remoteBooks) { // 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; candidates.Add(new CandidateEdition { Edition = edition, ExistingFiles = new List <BookFile>() }); } } watch.Stop(); _logger.Debug($"Getting {candidates.Count} remote candidates from tags for {localEdition.LocalBooks.Count} tracks took {watch.ElapsedMilliseconds}ms"); return(candidates); }
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); }