mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-27 22:56:45 -04:00
Whole album matching and fingerprinting (#592)
* Cache result of GetAllArtists
* Fixed: Manual import not respecting album import notifications
* Fixed: partial album imports stay in queue, prompting manual import
* Fixed: Allow release if tracks are missing
* Fixed: Be tolerant of missing/extra "The" at start of artist name
* Improve manual import UI
* Omit video tracks from DB entirely
* Revert "faster test packaging in build.sh"
This reverts commit 2723e2a7b8.
-u and -T are not supported on macOS
* Fix tests on linux and macOS
* Actually lint on linux
On linux yarn runs scripts with sh not bash so ** doesn't recursively glob
* Match whole albums
* Option to disable fingerprinting
* Rip out MediaInfo
* Don't split up things that have the same album selected in manual import
* Try to speed up IndentificationService
* More speedups
* Some fixes and increase power of recording id
* Fix NRE when no tags
* Fix NRE when some (but not all) files in a directory have missing tags
* Bump taglib, tidy up tag parsing
* Add a health check
* Remove media info setting
* Tags -> audioTags
* Add some tests where tags are null
* Rename history events
* Add missing method to interface
* Reinstate MediaInfo tags and update info with artist scan
Also adds migration to remove old format media info
* This file no longer exists
* Don't penalise year if missing from tags
* Formatting improvements
* Use correct system newline
* Switch to the netstandard2.0 library to support net 461
* TagLib.File is IDisposable so should be in a using
* Improve filename matching and add tests
* Neater logging of parsed tags
* Fix disk scan tests for new media info update
* Fix quality detection source
* Fix Inexact Artist/Album match
* Add button to clear track mapping
* Fix warning
* Pacify eslint
* Use \ not /
* Fix UI updates
* Fix media covers
Prevent localizing URL propaging back to the metadata object
* Reduce database overhead broadcasting UI updates
* Relax timings a bit to make test pass
* Remove irrelevant tests
* Test framework for identification service
* Fix PreferMissingToBadMatch test case
* Make fingerprinting more robust
* More logging
* Penalize unknown media format and country
* Prefer USA to UK
* Allow Data CD
* Fix exception if fingerprinting fails for all files
* Fix tests
* Fix NRE
* Allow apostrophes and remove accents in filename aggregation
* Address codacy issues
* Cope with old versions of fpcalc and suggest upgrade
* fpcalc health check passes if fingerprinting disabled
* Get the Artist meta with the artist
* Fix the mapper so that lazy loaded lists will be populated on Join
And therefore we can join TrackFiles on Tracks by default and avoid an
extra query
* Rename subtitle -> lyric
* Tidy up MediaInfoFormatter
This commit is contained in:
@@ -12,6 +12,7 @@ using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
using NzbDrone.Test.Common;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
||||
{
|
||||
@@ -44,6 +45,14 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
||||
Mocker.GetMock<IRootFolderService>()
|
||||
.Setup(s => s.GetBestRootFolderPath(It.IsAny<string>()))
|
||||
.Returns(_rootFolder);
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Setup(v => v.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Artist>()))
|
||||
.Returns(new List<ImportDecision<LocalTrack>>());
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(v => v.GetFilesByArtist(It.IsAny<int>()))
|
||||
.Returns(new List<TrackFile>());
|
||||
}
|
||||
|
||||
private void GivenRootFolder(params string[] subfolders)
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IImportApprovedTracks>()
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto))
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision<LocalTrack>>>(), true, null, ImportMode.Auto))
|
||||
.Returns(new List<ImportResult>());
|
||||
|
||||
var downloadItem = Builder<DownloadClientItem>.CreateNew()
|
||||
@@ -76,15 +76,15 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
{
|
||||
var localTrack = new LocalTrack();
|
||||
|
||||
var imported = new List<ImportDecision>();
|
||||
imported.Add(new ImportDecision(localTrack));
|
||||
var imported = new List<ImportDecision<LocalTrack>>();
|
||||
imported.Add(new ImportDecision<LocalTrack>(localTrack));
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Setup(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Artist>(), null))
|
||||
.Returns(imported);
|
||||
|
||||
Mocker.GetMock<IImportApprovedTracks>()
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), It.IsAny<bool>(), It.IsAny<DownloadClientItem>(), It.IsAny<ImportMode>()))
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision<LocalTrack>>>(), It.IsAny<bool>(), It.IsAny<DownloadClientItem>(), It.IsAny<ImportMode>()))
|
||||
.Returns(imported.Select(i => new ImportResult(i)).ToList())
|
||||
.Callback(() => WasImportedResponse());
|
||||
}
|
||||
@@ -155,7 +155,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
public void should_not_delete_folder_if_no_files_were_imported()
|
||||
{
|
||||
Mocker.GetMock<IImportApprovedTracks>()
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), false, null, ImportMode.Auto))
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision<LocalTrack>>>(), false, null, ImportMode.Auto))
|
||||
.Returns(new List<ImportResult>());
|
||||
|
||||
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
|
||||
@@ -171,15 +171,15 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
|
||||
var localTrack = new LocalTrack();
|
||||
|
||||
var imported = new List<ImportDecision>();
|
||||
imported.Add(new ImportDecision(localTrack));
|
||||
var imported = new List<ImportDecision<LocalTrack>>();
|
||||
imported.Add(new ImportDecision<LocalTrack>(localTrack));
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Setup(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Artist>(), null))
|
||||
.Returns(imported);
|
||||
|
||||
Mocker.GetMock<IImportApprovedTracks>()
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto))
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision<LocalTrack>>>(), true, null, ImportMode.Auto))
|
||||
.Returns(imported.Select(i => new ImportResult(i)).ToList());
|
||||
|
||||
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
|
||||
@@ -225,8 +225,8 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
|
||||
result.Should().HaveCount(1);
|
||||
result.First().ImportDecision.Should().NotBeNull();
|
||||
result.First().ImportDecision.LocalTrack.Should().NotBeNull();
|
||||
result.First().ImportDecision.LocalTrack.Path.Should().Be(fileName);
|
||||
result.First().ImportDecision.Item.Should().NotBeNull();
|
||||
result.First().ImportDecision.Item.Path.Should().Be(fileName);
|
||||
result.First().Result.Should().Be(ImportResultType.Rejected);
|
||||
}
|
||||
|
||||
@@ -237,15 +237,15 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
|
||||
var localTrack = new LocalTrack();
|
||||
|
||||
var imported = new List<ImportDecision>();
|
||||
imported.Add(new ImportDecision(localTrack));
|
||||
var imported = new List<ImportDecision<LocalTrack>>();
|
||||
imported.Add(new ImportDecision<LocalTrack>(localTrack));
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Setup(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Artist>(), null))
|
||||
.Returns(imported);
|
||||
|
||||
Mocker.GetMock<IImportApprovedTracks>()
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto))
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision<LocalTrack>>>(), true, null, ImportMode.Auto))
|
||||
.Returns(imported.Select(i => new ImportResult(i)).ToList());
|
||||
|
||||
//Mocker.GetMock<IDetectSample>()
|
||||
@@ -272,56 +272,6 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_folder_if_folder_import()
|
||||
{
|
||||
GivenValidArtist();
|
||||
|
||||
var folderName = @"C:\media\ba09030e-1234-1234-1234-123456789abc\[HorribleSubs] Maria the Virgin Witch - 09 [720p]".AsOsAgnostic();
|
||||
var fileName = @"C:\media\ba09030e-1234-1234-1234-123456789abc\[HorribleSubs] Maria the Virgin Witch - 09 [720p]\[HorribleSubs] Maria the Virgin Witch - 09 [720p].mkv".AsOsAgnostic();
|
||||
|
||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists(folderName))
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.GetFiles(folderName, SearchOption.TopDirectoryOnly))
|
||||
.Returns(new[] { fileName });
|
||||
|
||||
var localTrack = new LocalTrack();
|
||||
|
||||
var imported = new List<ImportDecision>();
|
||||
imported.Add(new ImportDecision(localTrack));
|
||||
|
||||
|
||||
Subject.ProcessPath(fileName);
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Artist>(), It.Is<ParsedTrackInfo>(v => v.TrackNumbers.First() == 9)), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_use_folder_if_file_import()
|
||||
{
|
||||
GivenValidArtist();
|
||||
|
||||
var fileName = @"C:\media\ba09030e-1234-1234-1234-123456789abc\Torrents\[HorribleSubs] Maria the Virgin Witch - 09 [720p].mkv".AsOsAgnostic();
|
||||
|
||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists(fileName))
|
||||
.Returns(false);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.FileExists(fileName))
|
||||
.Returns(true);
|
||||
|
||||
var localTrack = new LocalTrack();
|
||||
|
||||
var imported = new List<ImportDecision>();
|
||||
imported.Add(new ImportDecision(localTrack));
|
||||
|
||||
var result = Subject.ProcessPath(fileName);
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Artist>(), null), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_process_if_file_and_folder_do_not_exist()
|
||||
{
|
||||
@@ -348,15 +298,15 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
|
||||
var localTrack = new LocalTrack();
|
||||
|
||||
var imported = new List<ImportDecision>();
|
||||
imported.Add(new ImportDecision(localTrack));
|
||||
var imported = new List<ImportDecision<LocalTrack>>();
|
||||
imported.Add(new ImportDecision<LocalTrack>(localTrack));
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Setup(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Artist>(), null))
|
||||
.Returns(imported);
|
||||
|
||||
Mocker.GetMock<IImportApprovedTracks>()
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto))
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision<LocalTrack>>>(), true, null, ImportMode.Auto))
|
||||
.Returns(new List<ImportResult>());
|
||||
|
||||
//Mocker.GetMock<IDetectSample>()
|
||||
@@ -424,13 +374,13 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
|
||||
private void VerifyNoImport()
|
||||
{
|
||||
Mocker.GetMock<IImportApprovedTracks>().Verify(c => c.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto),
|
||||
Mocker.GetMock<IImportApprovedTracks>().Verify(c => c.Import(It.IsAny<List<ImportDecision<LocalTrack>>>(), true, null, ImportMode.Auto),
|
||||
Times.Never());
|
||||
}
|
||||
|
||||
private void VerifyImport()
|
||||
{
|
||||
Mocker.GetMock<IImportApprovedTracks>().Verify(c => c.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto),
|
||||
Mocker.GetMock<IImportApprovedTracks>().Verify(c => c.Import(It.IsAny<List<ImportDecision<LocalTrack>>>(), true, null, ImportMode.Auto),
|
||||
Times.Once());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,16 +25,16 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
[TestFixture]
|
||||
public class ImportApprovedTracksFixture : CoreTest<ImportApprovedTracks>
|
||||
{
|
||||
private List<ImportDecision> _rejectedDecisions;
|
||||
private List<ImportDecision> _approvedDecisions;
|
||||
private List<ImportDecision<LocalTrack>> _rejectedDecisions;
|
||||
private List<ImportDecision<LocalTrack>> _approvedDecisions;
|
||||
|
||||
private DownloadClientItem _downloadClientItem;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_rejectedDecisions = new List<ImportDecision>();
|
||||
_approvedDecisions = new List<ImportDecision>();
|
||||
_rejectedDecisions = new List<ImportDecision<LocalTrack>>();
|
||||
_approvedDecisions = new List<ImportDecision<LocalTrack>>();
|
||||
|
||||
var artist = Builder<Artist>.CreateNew()
|
||||
.With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() })
|
||||
@@ -52,20 +52,21 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
|
||||
var release = Builder<AlbumRelease>.CreateNew()
|
||||
.With(e => e.AlbumId = album.Id)
|
||||
.With(e => e.Monitored = true)
|
||||
.Build();
|
||||
|
||||
album.AlbumReleases = new List<AlbumRelease> { release };
|
||||
|
||||
var tracks = Builder<Track>.CreateListOfSize(5)
|
||||
.Build();
|
||||
|
||||
|
||||
|
||||
_rejectedDecisions.Add(new ImportDecision(new LocalTrack(), new Rejection("Rejected!")));
|
||||
_rejectedDecisions.Add(new ImportDecision(new LocalTrack(), new Rejection("Rejected!")));
|
||||
_rejectedDecisions.Add(new ImportDecision(new LocalTrack(), new Rejection("Rejected!")));
|
||||
_rejectedDecisions.Add(new ImportDecision<LocalTrack>(new LocalTrack(), new Rejection("Rejected!")));
|
||||
_rejectedDecisions.Add(new ImportDecision<LocalTrack>(new LocalTrack(), new Rejection("Rejected!")));
|
||||
_rejectedDecisions.Add(new ImportDecision<LocalTrack>(new LocalTrack(), new Rejection("Rejected!")));
|
||||
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
_approvedDecisions.Add(new ImportDecision
|
||||
_approvedDecisions.Add(new ImportDecision<LocalTrack>
|
||||
(
|
||||
new LocalTrack
|
||||
{
|
||||
@@ -75,7 +76,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
Tracks = new List<Track> { track },
|
||||
Path = Path.Combine(artist.Path, "Alien Ant Farm - 01 - Pilot.mp3"),
|
||||
Quality = new QualityModel(Quality.MP3_256),
|
||||
ParsedTrackInfo = new ParsedTrackInfo
|
||||
FileTrackInfo = new ParsedTrackInfo
|
||||
{
|
||||
ReleaseGroup = "DRONE"
|
||||
}
|
||||
@@ -91,6 +92,11 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(s => s.GetFilesWithRelativePath(It.IsAny<int>(), It.IsAny<string>()))
|
||||
.Returns(new List<TrackFile>());
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(s => s.GetFilesByAlbum(It.IsAny<int>()))
|
||||
.Returns(new List<TrackFile>());
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -110,7 +116,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
[Test]
|
||||
public void should_only_import_approved()
|
||||
{
|
||||
var all = new List<ImportDecision>();
|
||||
var all = new List<ImportDecision<LocalTrack>>();
|
||||
all.AddRange(_rejectedDecisions);
|
||||
all.AddRange(_approvedDecisions);
|
||||
|
||||
@@ -123,9 +129,9 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
[Test]
|
||||
public void should_only_import_each_track_once()
|
||||
{
|
||||
var all = new List<ImportDecision>();
|
||||
var all = new List<ImportDecision<LocalTrack>>();
|
||||
all.AddRange(_approvedDecisions);
|
||||
all.Add(new ImportDecision(_approvedDecisions.First().LocalTrack));
|
||||
all.Add(new ImportDecision<LocalTrack>(_approvedDecisions.First().Item));
|
||||
|
||||
var result = Subject.Import(all, false);
|
||||
|
||||
@@ -135,17 +141,17 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
[Test]
|
||||
public void should_move_new_downloads()
|
||||
{
|
||||
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
|
||||
Subject.Import(new List<ImportDecision<LocalTrack>> { _approvedDecisions.First() }, true);
|
||||
|
||||
Mocker.GetMock<IUpgradeMediaFiles>()
|
||||
.Verify(v => v.UpgradeTrackFile(It.IsAny<TrackFile>(), _approvedDecisions.First().LocalTrack, false),
|
||||
.Verify(v => v.UpgradeTrackFile(It.IsAny<TrackFile>(), _approvedDecisions.First().Item, false),
|
||||
Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_publish_TrackImportedEvent_for_new_downloads()
|
||||
{
|
||||
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
|
||||
Subject.Import(new List<ImportDecision<LocalTrack>> { _approvedDecisions.First() }, true);
|
||||
|
||||
Mocker.GetMock<IEventAggregator>()
|
||||
.Verify(v => v.PublishEvent(It.IsAny<TrackImportedEvent>()), Times.Once());
|
||||
@@ -154,10 +160,10 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
[Test]
|
||||
public void should_not_move_existing_files()
|
||||
{
|
||||
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, false);
|
||||
Subject.Import(new List<ImportDecision<LocalTrack>> { _approvedDecisions.First() }, false);
|
||||
|
||||
Mocker.GetMock<IUpgradeMediaFiles>()
|
||||
.Verify(v => v.UpgradeTrackFile(It.IsAny<TrackFile>(), _approvedDecisions.First().LocalTrack, false),
|
||||
.Verify(v => v.UpgradeTrackFile(It.IsAny<TrackFile>(), _approvedDecisions.First().Item, false),
|
||||
Times.Never());
|
||||
}
|
||||
|
||||
@@ -165,21 +171,21 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
public void should_import_larger_files_first()
|
||||
{
|
||||
var fileDecision = _approvedDecisions.First();
|
||||
fileDecision.LocalTrack.Size = 1.Gigabytes();
|
||||
fileDecision.Item.Size = 1.Gigabytes();
|
||||
|
||||
var sampleDecision = new ImportDecision
|
||||
var sampleDecision = new ImportDecision<LocalTrack>
|
||||
(new LocalTrack
|
||||
{
|
||||
Artist = fileDecision.LocalTrack.Artist,
|
||||
Album = fileDecision.LocalTrack.Album,
|
||||
Tracks = new List<Track> { fileDecision.LocalTrack.Tracks.First() },
|
||||
Artist = fileDecision.Item.Artist,
|
||||
Album = fileDecision.Item.Album,
|
||||
Tracks = new List<Track> { fileDecision.Item.Tracks.First() },
|
||||
Path = @"C:\Test\Music\Alien Ant Farm\Alien Ant Farm - 01 - Pilot.mp3".AsOsAgnostic(),
|
||||
Quality = new QualityModel(Quality.MP3_256),
|
||||
Size = 80.Megabytes()
|
||||
});
|
||||
|
||||
|
||||
var all = new List<ImportDecision>();
|
||||
var all = new List<ImportDecision<LocalTrack>>();
|
||||
all.Add(fileDecision);
|
||||
all.Add(sampleDecision);
|
||||
|
||||
@@ -187,25 +193,25 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
|
||||
results.Should().HaveCount(all.Count);
|
||||
results.Should().ContainSingle(d => d.Result == ImportResultType.Imported);
|
||||
results.Should().ContainSingle(d => d.Result == ImportResultType.Imported && d.ImportDecision.LocalTrack.Size == fileDecision.LocalTrack.Size);
|
||||
results.Should().ContainSingle(d => d.Result == ImportResultType.Imported && d.ImportDecision.Item.Size == fileDecision.Item.Size);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_copy_when_cannot_move_files_downloads()
|
||||
{
|
||||
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, new DownloadClientItem { Title = "Alien.Ant.Farm-Truant", CanMoveFiles = false });
|
||||
Subject.Import(new List<ImportDecision<LocalTrack>> { _approvedDecisions.First() }, true, new DownloadClientItem { Title = "Alien.Ant.Farm-Truant", CanMoveFiles = false });
|
||||
|
||||
Mocker.GetMock<IUpgradeMediaFiles>()
|
||||
.Verify(v => v.UpgradeTrackFile(It.IsAny<TrackFile>(), _approvedDecisions.First().LocalTrack, true), Times.Once());
|
||||
.Verify(v => v.UpgradeTrackFile(It.IsAny<TrackFile>(), _approvedDecisions.First().Item, true), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_override_importmode()
|
||||
{
|
||||
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, new DownloadClientItem { Title = "Alien.Ant.Farm-Truant", CanMoveFiles = false }, ImportMode.Move);
|
||||
Subject.Import(new List<ImportDecision<LocalTrack>> { _approvedDecisions.First() }, true, new DownloadClientItem { Title = "Alien.Ant.Farm-Truant", CanMoveFiles = false }, ImportMode.Move);
|
||||
|
||||
Mocker.GetMock<IUpgradeMediaFiles>()
|
||||
.Verify(v => v.UpgradeTrackFile(It.IsAny<TrackFile>(), _approvedDecisions.First().LocalTrack, false), Times.Once());
|
||||
.Verify(v => v.UpgradeTrackFile(It.IsAny<TrackFile>(), _approvedDecisions.First().Item, false), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -215,7 +221,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
.Setup(s => s.GetFilesWithRelativePath(It.IsAny<int>(), It.IsAny<string>()))
|
||||
.Returns(Builder<TrackFile>.CreateListOfSize(1).BuildList());
|
||||
|
||||
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, false);
|
||||
Subject.Import(new List<ImportDecision<LocalTrack>> { _approvedDecisions.First() }, false);
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(v => v.Delete(It.IsAny<TrackFile>(), DeleteMediaFileReason.ManualOverride), Times.Once());
|
||||
|
||||
@@ -5,32 +5,31 @@ using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles
|
||||
{
|
||||
[TestFixture]
|
||||
public class MediaFileRepositoryFixture : DbTest<MediaFileRepository, TrackFile>
|
||||
{
|
||||
[Test]
|
||||
public void get_files_by_artist()
|
||||
private Artist artist;
|
||||
private Album album;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
|
||||
var files = Builder<TrackFile>.CreateListOfSize(10)
|
||||
.All()
|
||||
.With(c => c.Id = 0)
|
||||
.With(c => c.Quality =new QualityModel(Quality.MP3_192))
|
||||
.BuildListOfNew();
|
||||
|
||||
Db.InsertMany(files);
|
||||
Db.All<TrackFile>().Should().HaveCount(10);
|
||||
var meta = Builder<ArtistMetadata>.CreateNew()
|
||||
.With(a => a.Id = 0)
|
||||
.Build();
|
||||
Db.Insert(meta);
|
||||
|
||||
var artist = Builder<Artist>.CreateNew()
|
||||
.With(a => a.ArtistMetadataId = 11)
|
||||
artist = Builder<Artist>.CreateNew()
|
||||
.With(a => a.ArtistMetadataId = meta.Id)
|
||||
.With(a => a.Id = 0)
|
||||
.Build();
|
||||
Db.Insert(artist);
|
||||
|
||||
var album = Builder<Album>.CreateNew()
|
||||
album = Builder<Album>.CreateNew()
|
||||
.With(a => a.Id = 0)
|
||||
.With(a => a.ArtistMetadataId = artist.ArtistMetadataId)
|
||||
.Build();
|
||||
@@ -43,6 +42,15 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
.Build();
|
||||
Db.Insert(release);
|
||||
|
||||
var files = Builder<TrackFile>.CreateListOfSize(10)
|
||||
.All()
|
||||
.With(c => c.Id = 0)
|
||||
.With(c => c.Quality =new QualityModel(Quality.MP3_192))
|
||||
.TheFirst(4)
|
||||
.With(c => c.AlbumId = album.Id)
|
||||
.BuildListOfNew();
|
||||
Db.InsertMany(files);
|
||||
|
||||
var track = Builder<Track>.CreateListOfSize(10)
|
||||
.TheFirst(1)
|
||||
.With(a => a.TrackFileId = files[1].Id)
|
||||
@@ -59,16 +67,58 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
.With(a => a.AlbumReleaseId = release.Id)
|
||||
.Build();
|
||||
Db.InsertMany(track);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_files_by_artist()
|
||||
{
|
||||
VerifyData();
|
||||
var artistFiles = Subject.GetFilesByArtist(artist.Id);
|
||||
VerifyEagerLoaded(artistFiles);
|
||||
|
||||
artistFiles.Should().HaveCount(4);
|
||||
artistFiles.Should().OnlyContain(c => c.Artist.Value.Id == artist.Id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_files_by_album()
|
||||
{
|
||||
VerifyData();
|
||||
var files = Subject.GetFilesByAlbum(album.Id);
|
||||
VerifyEagerLoaded(files);
|
||||
|
||||
files.Should().HaveCount(4);
|
||||
files.Should().OnlyContain(c => c.AlbumId == album.Id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_files_by_relative_path()
|
||||
{
|
||||
VerifyData();
|
||||
var files = Subject.GetFilesWithRelativePath(artist.Id, "RelativePath2");
|
||||
VerifyEagerLoaded(files);
|
||||
|
||||
files.Should().HaveCount(1);
|
||||
files.Should().OnlyContain(c => c.AlbumId == album.Id);
|
||||
files.Should().OnlyContain(c => c.RelativePath == "RelativePath2");
|
||||
}
|
||||
|
||||
private void VerifyData()
|
||||
{
|
||||
Db.All<Artist>().Should().HaveCount(1);
|
||||
Db.All<Album>().Should().HaveCount(1);
|
||||
Db.All<Track>().Should().HaveCount(10);
|
||||
Db.All<TrackFile>().Should().HaveCount(10);
|
||||
}
|
||||
|
||||
var artistFiles = Subject.GetFilesByArtist(artist.Id);
|
||||
|
||||
artistFiles.Should().HaveCount(4);
|
||||
|
||||
artistFiles.Should().OnlyContain(c => c.ArtistId == artist.Id);
|
||||
private void VerifyEagerLoaded(List<TrackFile> files)
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
file.Album.IsLoaded.Should().BeTrue();
|
||||
file.Artist.IsLoaded.Should().BeTrue();
|
||||
file.Artist.Value.Metadata.IsLoaded.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-149
@@ -1,149 +0,0 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class FormatAudioChannelsFixture : TestBase
|
||||
{
|
||||
[Test]
|
||||
public void should_subtract_one_from_AudioChannels_as_total_channels_if_LFE_in_AudioChannelPositionsText()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioChannels = 6,
|
||||
AudioChannelPositions = null,
|
||||
AudioChannelPositionsText = "Front: L C R, Side: L R, LFE"
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_AudioChannels_as_total_channels_if_LFE_not_in_AudioChannelPositionsText()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioChannels = 2,
|
||||
AudioChannelPositions = null,
|
||||
AudioChannelPositionsText = "Front: L R"
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_0_if_schema_revision_is_less_than_3_and_other_properties_are_null()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioChannels = 2,
|
||||
AudioChannelPositions = null,
|
||||
AudioChannelPositionsText = null,
|
||||
SchemaRevision = 2
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_AudioChannels_if_schema_revision_is_3_and_other_properties_are_null()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioChannels = 2,
|
||||
AudioChannelPositions = null,
|
||||
AudioChannelPositionsText = null,
|
||||
SchemaRevision = 3
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_sum_AudioChannelPositions()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioChannels = 2,
|
||||
AudioChannelPositions = "2/0/0",
|
||||
AudioChannelPositionsText = null,
|
||||
SchemaRevision = 3
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_sum_AudioChannelPositions_including_decimal()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioChannels = 2,
|
||||
AudioChannelPositions = "3/2/0.1",
|
||||
AudioChannelPositionsText = null,
|
||||
SchemaRevision = 3
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_cleanup_extraneous_text_from_AudioChannelPositions()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioChannels = 2,
|
||||
AudioChannelPositions = "Object Based / 3/2/2.1",
|
||||
AudioChannelPositionsText = null,
|
||||
SchemaRevision = 3
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_skip_empty_groups_in_AudioChannelPositions()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioChannels = 2,
|
||||
AudioChannelPositions = " / 2/0/0.0",
|
||||
AudioChannelPositionsText = null,
|
||||
SchemaRevision = 3
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_sum_first_series_of_numbers_from_AudioChannelPositions()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioChannels = 2,
|
||||
AudioChannelPositions = "3/2/2.1 / 3/2/2.1",
|
||||
AudioChannelPositionsText = null,
|
||||
SchemaRevision = 3
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_sum_dual_mono_representation_AudioChannelPositions()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioChannels = 2,
|
||||
AudioChannelPositions = "1+1",
|
||||
AudioChannelPositionsText = null,
|
||||
SchemaRevision = 3
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2.0m);
|
||||
}
|
||||
}
|
||||
}
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class FormatAudioCodecFixture : TestBase
|
||||
{
|
||||
[TestCase("AC-3", "AC3")]
|
||||
[TestCase("E-AC-3", "EAC3")]
|
||||
[TestCase("MPEG Audio", "MPEG Audio")]
|
||||
[TestCase("DTS", "DTS")]
|
||||
public void should_format_audio_format(string audioFormat, string expectedFormat)
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioFormat = audioFormat
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel).Should().Be(expectedFormat);
|
||||
}
|
||||
|
||||
[TestCase("MPEG Audio, A_MPEG/L2, , ", "droned.s01e03.swedish.720p.hdtv.x264-prince", "MP2")]
|
||||
[TestCase("Vorbis, A_VORBIS, , Xiph.Org libVorbis I 20101101 (Schaufenugget)", "DB Super HDTV", "Vorbis")]
|
||||
[TestCase("PCM, 1, , ", "DW DVDRip XviD-idTV", "PCM")] // Dubbed most likely
|
||||
[TestCase("TrueHD, A_TRUEHD, , ", "", "TrueHD")]
|
||||
[TestCase("WMA, 161, , ", "Droned.wmv", "WMA")]
|
||||
[TestCase("WMA, 162, Pro, ", "B.N.S04E18.720p.WEB-DL", "WMA")]
|
||||
public void should_format_audio_format(string audioFormatPack, string sceneName, string expectedFormat)
|
||||
{
|
||||
var split = audioFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioFormat = split[0],
|
||||
AudioCodecID = split[1],
|
||||
AudioProfile = split[2],
|
||||
AudioCodecLibrary = split[3]
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel).Should().Be(expectedFormat);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_MP3_for_MPEG_Audio_with_Layer_3_for_the_profile()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioFormat = "MPEG Audio",
|
||||
AudioProfile = "Layer 3"
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel).Should().Be("MP3");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_AudioFormat_by_default()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioFormat = "Other Audio Format",
|
||||
AudioCodecID = "Other Audio Codec"
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel).Should().Be(mediaInfoModel.AudioFormat);
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
using System.IO;
|
||||
using FizzWare.NBuilder;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Test.Common;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
||||
{
|
||||
[TestFixture]
|
||||
public class UpdateMediaInfoServiceFixture : CoreTest<UpdateMediaInfoService>
|
||||
{
|
||||
private Artist _artist;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_artist = new Artist
|
||||
{
|
||||
Id = 1,
|
||||
Path = @"C:\artist".AsOsAgnostic()
|
||||
};
|
||||
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.SetupGet(s => s.EnableMediaInfo)
|
||||
.Returns(true);
|
||||
}
|
||||
|
||||
private void GivenFileExists()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.FileExists(It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
}
|
||||
|
||||
private void GivenSuccessfulScan()
|
||||
{
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Setup(v => v.GetMediaInfo(It.IsAny<string>()))
|
||||
.Returns(new MediaInfoModel());
|
||||
}
|
||||
|
||||
private void GivenFailedScan(string path)
|
||||
{
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Setup(v => v.GetMediaInfo(path))
|
||||
.Returns((MediaInfoModel)null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_skip_up_to_date_media_info()
|
||||
{
|
||||
var trackFiles = Builder<TrackFile>.CreateListOfSize(3)
|
||||
.All()
|
||||
.With(v => v.RelativePath = "media.flac")
|
||||
.TheFirst(1)
|
||||
.With(v => v.MediaInfo = new MediaInfoModel { SchemaRevision = VideoFileInfoReader.CURRENT_MEDIA_INFO_SCHEMA_REVISION })
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(v => v.GetFilesByArtist(1))
|
||||
.Returns(trackFiles);
|
||||
|
||||
GivenFileExists();
|
||||
GivenSuccessfulScan();
|
||||
|
||||
Subject.Handle(new ArtistScannedEvent(_artist));
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.flac")), Times.Exactly(2));
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(v => v.Update(It.IsAny<TrackFile>()), Times.Exactly(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_skip_not_yet_date_media_info()
|
||||
{
|
||||
var trackFiles = Builder<TrackFile>.CreateListOfSize(3)
|
||||
.All()
|
||||
.With(v => v.RelativePath = "media.flac")
|
||||
.TheFirst(1)
|
||||
.With(v => v.MediaInfo = new MediaInfoModel { SchemaRevision = VideoFileInfoReader.MINIMUM_MEDIA_INFO_SCHEMA_REVISION })
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(v => v.GetFilesByArtist(1))
|
||||
.Returns(trackFiles);
|
||||
|
||||
GivenFileExists();
|
||||
GivenSuccessfulScan();
|
||||
|
||||
Subject.Handle(new ArtistScannedEvent(_artist));
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.flac")), Times.Exactly(2));
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(v => v.Update(It.IsAny<TrackFile>()), Times.Exactly(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_update_outdated_media_info()
|
||||
{
|
||||
var trackFiles = Builder<TrackFile>.CreateListOfSize(3)
|
||||
.All()
|
||||
.With(v => v.RelativePath = "media.flac")
|
||||
.TheFirst(1)
|
||||
.With(v => v.MediaInfo = new MediaInfoModel())
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(v => v.GetFilesByArtist(1))
|
||||
.Returns(trackFiles);
|
||||
|
||||
GivenFileExists();
|
||||
GivenSuccessfulScan();
|
||||
|
||||
Subject.Handle(new ArtistScannedEvent(_artist));
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.flac")), Times.Exactly(3));
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(v => v.Update(It.IsAny<TrackFile>()), Times.Exactly(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_ignore_missing_files()
|
||||
{
|
||||
var trackFiles = Builder<TrackFile>.CreateListOfSize(2)
|
||||
.All()
|
||||
.With(v => v.RelativePath = "media.flac")
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(v => v.GetFilesByArtist(1))
|
||||
.Returns(trackFiles);
|
||||
|
||||
GivenSuccessfulScan();
|
||||
|
||||
Subject.Handle(new ArtistScannedEvent(_artist));
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Verify(v => v.GetMediaInfo("media.flac"), Times.Never());
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(v => v.Update(It.IsAny<TrackFile>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_continue_after_failure()
|
||||
{
|
||||
var episodeFiles = Builder<TrackFile>.CreateListOfSize(2)
|
||||
.All()
|
||||
.With(v => v.RelativePath = "media.flac")
|
||||
.TheFirst(1)
|
||||
.With(v => v.RelativePath = "media2.flac")
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(v => v.GetFilesByArtist(1))
|
||||
.Returns(episodeFiles);
|
||||
|
||||
GivenFileExists();
|
||||
GivenSuccessfulScan();
|
||||
GivenFailedScan(Path.Combine(_artist.Path, "media2.flac"));
|
||||
|
||||
Subject.Handle(new ArtistScannedEvent(_artist));
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.flac")), Times.Exactly(1));
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(v => v.Update(It.IsAny<TrackFile>()), Times.Exactly(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
using System.IO;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common.Categories;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
||||
{
|
||||
[TestFixture]
|
||||
[DiskAccessTest]
|
||||
public class VideoFileInfoReaderFixture : CoreTest<VideoFileInfoReader>
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.FileExists(It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.OpenReadStream(It.IsAny<string>()))
|
||||
.Returns<string>(s => new FileStream(s, FileMode.Open, FileAccess.Read));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_runtime()
|
||||
{
|
||||
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "H264_sample.mp4");
|
||||
|
||||
Subject.GetRunTime(path).Seconds.Should().Be(10);
|
||||
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void get_info()
|
||||
{
|
||||
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "H264_sample.mp4");
|
||||
|
||||
var info = Subject.GetMediaInfo(path);
|
||||
|
||||
info.VideoCodec.Should().BeNull();
|
||||
info.VideoFormat.Should().Be("AVC");
|
||||
info.VideoCodecID.Should().Be("avc1");
|
||||
info.VideoProfile.Should().Be("Baseline@L2.1");
|
||||
info.VideoCodecLibrary.Should().Be("");
|
||||
info.AudioFormat.Should().Be("AAC");
|
||||
info.AudioCodecID.Should().BeOneOf("40", "mp4a-40-2");
|
||||
info.AudioProfile.Should().Be("LC");
|
||||
info.AudioCodecLibrary.Should().Be("");
|
||||
info.AudioBitrate.Should().Be(128000);
|
||||
info.AudioChannels.Should().Be(2);
|
||||
info.AudioLanguages.Should().Be("English");
|
||||
info.Height.Should().Be(320);
|
||||
info.RunTime.Seconds.Should().Be(10);
|
||||
info.ScanType.Should().Be("Progressive");
|
||||
info.Subtitles.Should().Be("");
|
||||
info.VideoBitrate.Should().Be(193329);
|
||||
info.VideoFps.Should().Be(24);
|
||||
info.Width.Should().Be(480);
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_info_unicode()
|
||||
{
|
||||
var srcPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "H264_sample.mp4");
|
||||
|
||||
var tempPath = GetTempFilePath();
|
||||
Directory.CreateDirectory(tempPath);
|
||||
|
||||
var path = Path.Combine(tempPath, "H264_Pok\u00E9mon.mkv");
|
||||
|
||||
File.Copy(srcPath, path);
|
||||
|
||||
var info = Subject.GetMediaInfo(path);
|
||||
|
||||
info.VideoCodec.Should().BeNull();
|
||||
info.VideoFormat.Should().Be("AVC");
|
||||
info.VideoCodecID.Should().Be("avc1");
|
||||
info.VideoProfile.Should().Be("Baseline@L2.1");
|
||||
info.VideoCodecLibrary.Should().Be("");
|
||||
info.AudioFormat.Should().Be("AAC");
|
||||
info.AudioCodecID.Should().BeOneOf("40", "mp4a-40-2");
|
||||
info.AudioProfile.Should().Be("LC");
|
||||
info.AudioCodecLibrary.Should().Be("");
|
||||
info.AudioBitrate.Should().Be(128000);
|
||||
info.AudioChannels.Should().Be(2);
|
||||
info.AudioLanguages.Should().Be("English");
|
||||
info.Height.Should().Be(320);
|
||||
info.RunTime.Seconds.Should().Be(10);
|
||||
info.ScanType.Should().Be("Progressive");
|
||||
info.Subtitles.Should().Be("");
|
||||
info.VideoBitrate.Should().Be(193329);
|
||||
info.VideoFps.Should().Be(24);
|
||||
info.Width.Should().Be(480);
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_dispose_file_after_scanning_mediainfo()
|
||||
{
|
||||
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "H264_sample.mp4");
|
||||
|
||||
var info = Subject.GetMediaInfo(path);
|
||||
|
||||
var stream = new FileStream(path, FileMode.Open, FileAccess.Write);
|
||||
|
||||
stream.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using NzbDrone.Test.Common;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Aggregation.Aggregators;
|
||||
using FluentAssertions;
|
||||
using System.Text;
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Aggregation.Aggregators
|
||||
{
|
||||
[TestFixture]
|
||||
public class AggregateFilenameInfoFixture : CoreTest<AggregateFilenameInfo>
|
||||
{
|
||||
|
||||
private LocalAlbumRelease GivenTracks(List<string> files, string root)
|
||||
{
|
||||
var tracks = files.Select(x => new LocalTrack {
|
||||
Path = Path.Combine(root, x),
|
||||
FileTrackInfo = new ParsedTrackInfo {
|
||||
TrackNumbers = new [] { 0 },
|
||||
}
|
||||
}).ToList();
|
||||
return new LocalAlbumRelease(tracks);
|
||||
}
|
||||
|
||||
private void VerifyData(LocalTrack track, string artist, string title, int trackNum, int disc)
|
||||
{
|
||||
track.FileTrackInfo.ArtistTitle.Should().Be(artist);
|
||||
track.FileTrackInfo.Title.Should().Be(title);
|
||||
track.FileTrackInfo.TrackNumbers[0].Should().Be(trackNum);
|
||||
track.FileTrackInfo.DiscNumber.Should().Be(disc);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_aggregate_filenames_example()
|
||||
{
|
||||
var release = GivenTracks(new List<string> {
|
||||
"Adele - 19 - 101 - Daydreamer.mp3",
|
||||
"Adele - 19 - 102 - Best for Last.mp3",
|
||||
"Adele - 19 - 103 - Chasing Pavements.mp3",
|
||||
"Adele - 19 - 203 - That's It, I Quit, I'm Moving On.mp3"
|
||||
}, @"C:\incoming".AsOsAgnostic());
|
||||
|
||||
Subject.Aggregate(release, true);
|
||||
|
||||
VerifyData(release.LocalTracks[0], "Adele", "Daydreamer", 1, 1);
|
||||
VerifyData(release.LocalTracks[1], "Adele", "Best for Last", 2, 1);
|
||||
VerifyData(release.LocalTracks[2], "Adele", "Chasing Pavements", 3, 1);
|
||||
VerifyData(release.LocalTracks[3], "Adele", "That's It, I Quit, I'm Moving On", 3, 2);
|
||||
}
|
||||
|
||||
public static class TestCaseFactory
|
||||
{
|
||||
private static List<string[]> tokenList = new List<string[]> {
|
||||
|
||||
new [] {"trackNum2", "artist", "title", "tag"},
|
||||
new [] {"trackNum3", "artist", "title", "tag"},
|
||||
new [] {"trackNum2", "artist", "tag", "title"},
|
||||
new [] {"trackNum3", "artist", "tag", "title"},
|
||||
new [] {"trackNum2", "artist", "title"},
|
||||
new [] {"trackNum3", "artist", "title"},
|
||||
|
||||
new [] {"artist", "tag", "trackNum2", "title"},
|
||||
new [] {"artist", "tag", "trackNum3", "title"},
|
||||
new [] {"artist", "trackNum2", "title", "tag"},
|
||||
new [] {"artist", "trackNum3", "title", "tag"},
|
||||
new [] {"artist", "trackNum2", "title"},
|
||||
new [] {"artist", "trackNum3", "title"},
|
||||
|
||||
new [] {"artist", "title", "tag"},
|
||||
new [] {"artist", "tag", "title"},
|
||||
new [] {"artist", "title"},
|
||||
|
||||
new [] {"trackNum2", "title"},
|
||||
new [] {"trackNum3", "title"},
|
||||
|
||||
new [] {"title"},
|
||||
};
|
||||
|
||||
private static List<Tuple<string, string>> separators = new List<Tuple<string, string>> {
|
||||
Tuple.Create(" - ", " "),
|
||||
Tuple.Create("_", " "),
|
||||
Tuple.Create("-", "_")
|
||||
};
|
||||
|
||||
private static List<Tuple<string[], string, string>> otherCases = new List<Tuple<string[], string, string>> {
|
||||
Tuple.Create(new [] {"track2", "title"}, " ", " "),
|
||||
Tuple.Create(new [] {"track3", "title"}, " ", " ")
|
||||
};
|
||||
|
||||
public static IEnumerable TestCases
|
||||
{
|
||||
get
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
foreach (var tokens in tokenList)
|
||||
{
|
||||
foreach (var separator in separators)
|
||||
{
|
||||
i++;
|
||||
yield return new TestCaseData(Tuple.Create(tokens, separator.Item1, separator.Item2))
|
||||
.SetName($"should_aggregate_filenames_auto_{i}")
|
||||
.SetDescription($"tokens: {string.Join(", ", tokens)}, separator: '{separator.Item1}', whitespace: '{separator.Item2}'");
|
||||
}
|
||||
}
|
||||
|
||||
// and a few other cases where all the permutations don't make sense
|
||||
foreach (var item in otherCases)
|
||||
{
|
||||
i++;
|
||||
yield return new TestCaseData(item)
|
||||
.SetName($"should_aggregate_filenames_auto_{i}")
|
||||
.SetDescription($"tokens: {string.Join(", ", item.Item1)}, separator: '{item.Item2}', whitespace: '{item.Item3}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<string> GivenFilenames(string[] fields, string fieldSeparator, string whitespace)
|
||||
{
|
||||
var outp = new List<string>();
|
||||
for (int i = 1; i <= 3; i++)
|
||||
{
|
||||
var components = new List<string>();
|
||||
foreach (var field in fields)
|
||||
{
|
||||
switch(field)
|
||||
{
|
||||
case "artist":
|
||||
components.Add("artist name".Replace(" ", whitespace));
|
||||
break;
|
||||
case "tag":
|
||||
components.Add("tag string ignore".Replace(" ", whitespace));
|
||||
break;
|
||||
case "title":
|
||||
components.Add($"{(char)(96+i)} track title {i}".Replace(" ", whitespace));
|
||||
break;
|
||||
case "trackNum2":
|
||||
components.Add(i.ToString("00"));
|
||||
break;
|
||||
case "trackNum3":
|
||||
components.Add((100 + i).ToString("000"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
outp.Add(string.Join(fieldSeparator, components) + ".mp3");
|
||||
}
|
||||
|
||||
return outp;
|
||||
}
|
||||
|
||||
private void VerifyDataAuto(List<LocalTrack> tracks, string[] tokens, string whitespace)
|
||||
{
|
||||
for (int i = 1; i <= tracks.Count; i++)
|
||||
{
|
||||
var info = tracks[i-1].FileTrackInfo;
|
||||
|
||||
if (tokens.Contains("artist"))
|
||||
{
|
||||
info.ArtistTitle.Should().Be("artist name".Replace(" ", whitespace));
|
||||
}
|
||||
|
||||
if (tokens.Contains("title"))
|
||||
{
|
||||
info.Title.Should().Be($"{(char)(96+i)} track title {i}".Replace(" ", whitespace));
|
||||
}
|
||||
|
||||
if (tokens.Contains("trackNum2") || tokens.Contains("trackNum3"))
|
||||
{
|
||||
info.TrackNumbers[0].Should().Be(i);
|
||||
}
|
||||
|
||||
if (tokens.Contains("trackNum3"))
|
||||
{
|
||||
info.DiscNumber.Should().Be(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
info.DiscNumber.Should().Be(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(typeof(TestCaseFactory), "TestCases")]
|
||||
public void should_aggregate_filenames_auto(Tuple<string[], string, string> testcase)
|
||||
{
|
||||
var files = GivenFilenames(testcase.Item1, testcase.Item2, testcase.Item3);
|
||||
var release = GivenTracks(files, @"C:\incoming".AsOsAgnostic());
|
||||
|
||||
Subject.Aggregate(release, true);
|
||||
|
||||
VerifyDataAuto(release.LocalTracks, testcase.Item1, testcase.Item3);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
|
||||
using FluentAssertions;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using FizzWare.NBuilder;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Music;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using NzbDrone.Core.Parser;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
[TestFixture]
|
||||
public class AlbumDistanceFixture : CoreTest<IdentificationService>
|
||||
{
|
||||
|
||||
private ArtistMetadata artist;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
artist = Builder<ArtistMetadata>
|
||||
.CreateNew()
|
||||
.With(x => x.Name = "artist")
|
||||
.Build();
|
||||
}
|
||||
|
||||
private List<Track> GivenTracks(int count)
|
||||
{
|
||||
return Builder<Track>
|
||||
.CreateListOfSize(count)
|
||||
.All()
|
||||
.With(x => x.ArtistMetadata = artist)
|
||||
.With(x => x.MediumNumber = 1)
|
||||
.Build()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private LocalTrack GivenLocalTrack(Track track, AlbumRelease release)
|
||||
{
|
||||
var fileInfo = Builder<ParsedTrackInfo>
|
||||
.CreateNew()
|
||||
.With(x => x.Title = track.Title)
|
||||
.With(x => x.CleanTitle = track.Title.CleanTrackTitle())
|
||||
.With(x => x.AlbumTitle = release.Title)
|
||||
.With(x => x.Disambiguation = release.Disambiguation)
|
||||
.With(x => x.ReleaseMBId = release.ForeignReleaseId)
|
||||
.With(x => x.ArtistTitle = track.ArtistMetadata.Value.Name)
|
||||
.With(x => x.TrackNumbers = new[] { track.AbsoluteTrackNumber })
|
||||
.With(x => x.DiscCount = release.Media.Count)
|
||||
.With(x => x.DiscNumber = track.MediumNumber)
|
||||
.With(x => x.RecordingMBId = track.ForeignRecordingId)
|
||||
.With(x => x.Country = IsoCountries.Find("US"))
|
||||
.With(x => x.Label = release.Label.First())
|
||||
.With(x => x.Year = (uint)release.Album.Value.ReleaseDate.Value.Year)
|
||||
.Build();
|
||||
|
||||
var localTrack = Builder<LocalTrack>
|
||||
.CreateNew()
|
||||
.With(x => x.FileTrackInfo = fileInfo)
|
||||
.Build();
|
||||
|
||||
return localTrack;
|
||||
}
|
||||
|
||||
private List<LocalTrack> GivenLocalTracks(List<Track> tracks, AlbumRelease release)
|
||||
{
|
||||
var output = new List<LocalTrack>();
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
output.Add(GivenLocalTrack(track, release));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
private AlbumRelease GivenAlbumRelease(string title, List<Track> tracks)
|
||||
{
|
||||
var album = Builder<Album>
|
||||
.CreateNew()
|
||||
.With(x => x.Title = title)
|
||||
.With(x => x.ArtistMetadata = artist)
|
||||
.Build();
|
||||
|
||||
var media = Builder<Medium>
|
||||
.CreateListOfSize(tracks.Max(x => x.MediumNumber))
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
return Builder<AlbumRelease>
|
||||
.CreateNew()
|
||||
.With(x => x.Tracks = tracks)
|
||||
.With(x => x.Title = title)
|
||||
.With(x => x.Album = album)
|
||||
.With(x => x.Media = media)
|
||||
.With(x => x.Country = new List<string> { "United States" })
|
||||
.With(x => x.Label = new List<string> { "label" })
|
||||
.Build();
|
||||
}
|
||||
|
||||
private TrackMapping GivenMapping(List<LocalTrack> local, List<Track> remote)
|
||||
{
|
||||
var mapping = new TrackMapping();
|
||||
var distances = local.Zip(remote, (l, r) => Tuple.Create(r, Subject.TrackDistance(l, r, Subject.GetTotalTrackNumber(r, remote))));
|
||||
mapping.Mapping = local.Zip(distances, (l, r) => new { l, r }).ToDictionary(x => x.l, x => x.r);
|
||||
mapping.LocalExtra = local.Except(mapping.Mapping.Keys).ToList();
|
||||
mapping.MBExtra = remote.Except(mapping.Mapping.Values.Select(x => x.Item1)).ToList();
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_identical_albums()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
var mapping = GivenMapping(localTracks, tracks);
|
||||
|
||||
Subject.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_incomplete_album()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
localTracks.RemoveAt(1);
|
||||
var mapping = GivenMapping(localTracks, tracks);
|
||||
|
||||
var dist = Subject.AlbumReleaseDistance(localTracks, release, mapping);
|
||||
dist.NormalizedDistance().Should().NotBe(0.0);
|
||||
dist.NormalizedDistance().Should().BeLessThan(0.2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_global_artists_differ()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
var mapping = GivenMapping(localTracks, tracks);
|
||||
|
||||
release.Album.Value.ArtistMetadata = Builder<ArtistMetadata>
|
||||
.CreateNew()
|
||||
.With(x => x.Name = "different artist")
|
||||
.Build();
|
||||
|
||||
Subject.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance().Should().NotBe(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_comp_track_artists_match()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
var mapping = GivenMapping(localTracks, tracks);
|
||||
|
||||
release.Album.Value.ArtistMetadata = Builder<ArtistMetadata>
|
||||
.CreateNew()
|
||||
.With(x => x.Name = "Various Artists")
|
||||
.With(x => x.ForeignArtistId = "89ad4ac3-39f7-470e-963a-56509c546377")
|
||||
.Build();
|
||||
|
||||
Subject.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
|
||||
// TODO: there are a couple more VA tests in beets but we don't support VA yet anyway
|
||||
|
||||
[Test]
|
||||
public void test_tracks_out_of_order()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
localTracks = new [] {1, 3, 2}.Select(x => localTracks[x-1]).ToList();
|
||||
var mapping = GivenMapping(localTracks, tracks);
|
||||
|
||||
var dist = Subject.AlbumReleaseDistance(localTracks, release, mapping);
|
||||
dist.NormalizedDistance().Should().NotBe(0.0);
|
||||
dist.NormalizedDistance().Should().BeLessThan(0.2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_two_medium_release()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
tracks[2].AbsoluteTrackNumber = 1;
|
||||
tracks[2].MediumNumber = 2;
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
var mapping = GivenMapping(localTracks, tracks);
|
||||
|
||||
Subject.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_absolute_track_numbering()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
tracks[2].AbsoluteTrackNumber = 1;
|
||||
tracks[2].MediumNumber = 2;
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
localTracks[2].FileTrackInfo.DiscNumber = 2;
|
||||
localTracks[2].FileTrackInfo.TrackNumbers = new[] { 3 };
|
||||
|
||||
var mapping = GivenMapping(localTracks, tracks);
|
||||
|
||||
Subject.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
|
||||
using NzbDrone.Test.Common;
|
||||
using FluentAssertions;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
[TestFixture]
|
||||
public class DistanceFixture : TestBase
|
||||
{
|
||||
[Test]
|
||||
public void test_add()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.Add("add", 1.0);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"add", new List<double> { 1.0 }}} );
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_equality()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.AddEquality("equality", "ghi", new List<string> { "abc", "def", "ghi" });
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"equality", new List<double> { 0.0 }}} );
|
||||
|
||||
dist.AddEquality("equality", "xyz", new List<string> { "abc", "def", "ghi" });
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"equality", new List<double> { 0.0, 1.0 }}} );
|
||||
|
||||
dist.AddEquality("equality", "abc", new List<string> { "abc", "def", "ghi" });
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"equality", new List<double> { 0.0, 1.0, 0.0 }}} );
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_add_bool()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.AddBool("expr", true);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"expr", new List<double> { 1.0 }}} );
|
||||
|
||||
dist.AddBool("expr", false);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"expr", new List<double> { 1.0, 0.0 }}} );
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_add_number()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.AddNumber("number", 1, 1);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"number", new List<double> { 0.0 }}} );
|
||||
|
||||
dist.AddNumber("number", 1, 2);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"number", new List<double> { 0.0, 1.0 }}} );
|
||||
|
||||
dist.AddNumber("number", 2, 1);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"number", new List<double> { 0.0, 1.0, 1.0 }}} );
|
||||
|
||||
dist.AddNumber("number", -1, 2);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"number", new List<double> { 0.0, 1.0, 1.0, 1.0, 1.0, 1.0 }}} );
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_add_priority_value()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.AddPriority("priority", "abc", new List<string> { "abc" });
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"priority", new List<double> { 0.0 }}} );
|
||||
|
||||
dist.AddPriority("priority", "def", new List<string> { "abc", "def" });
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"priority", new List<double> { 0.0, 0.5 }}} );
|
||||
|
||||
dist.AddPriority("priority", "xyz", new List<string> { "abc", "def" });
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"priority", new List<double> { 0.0, 0.5, 1.0 }}} );
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_add_priority_list()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.AddPriority("priority", new List<string> { "abc" }, new List<string> { "abc" });
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"priority", new List<double> { 0.0 }}} );
|
||||
|
||||
dist.AddPriority("priority", new List<string> { "def" }, new List<string> { "abc" });
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"priority", new List<double> { 0.0, 1.0 }}} );
|
||||
|
||||
dist.AddPriority("priority", new List<string> { "abc", "xyz" }, new List<string> { "abc" });
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"priority", new List<double> { 0.0, 1.0, 0.0 }}} );
|
||||
|
||||
dist.AddPriority("priority", new List<string> { "def", "xyz" }, new List<string> { "abc", "def" });
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"priority", new List<double> { 0.0, 1.0, 0.0, 0.5 }}} );
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_add_ratio()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.AddRatio("ratio", 25, 100);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"ratio", new List<double> { 0.25 }}} );
|
||||
|
||||
dist.AddRatio("ratio", 10, 5);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"ratio", new List<double> { 0.25, 1.0 }}} );
|
||||
|
||||
dist.AddRatio("ratio", -5, 5);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"ratio", new List<double> { 0.25, 1.0, 0.0 }}} );
|
||||
|
||||
dist.AddRatio("ratio", 5, 0);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"ratio", new List<double> { 0.25, 1.0, 0.0, 0.0 }}} );
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_add_string()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.AddString("string", "abcd", "bcde");
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"string", new List<double> { 0.5 }}} );
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_add_string_none()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.AddString("string", string.Empty, "bcd");
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"string", new List<double> { 1.0 }}} );
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_add_string_both_none()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.AddString("string", string.Empty, string.Empty);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"string", new List<double> { 0.0 }}} );
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_distance()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.Add("album", 0.5);
|
||||
dist.Add("media_count", 0.25);
|
||||
dist.Add("media_count", 0.75);
|
||||
|
||||
dist.NormalizedDistance().Should().Be(0.5);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_max_distance()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.Add("album", 0.5);
|
||||
dist.Add("media_count", 0.0);
|
||||
dist.Add("media_count", 0.0);
|
||||
|
||||
dist.MaxDistance().Should().Be(5.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_raw_distance()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.Add("album", 0.5);
|
||||
dist.Add("media_count", 0.25);
|
||||
dist.Add("media_count", 0.5);
|
||||
|
||||
dist.RawDistance().Should().Be(2.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
|
||||
using FluentAssertions;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using FizzWare.NBuilder;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Music;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using Moq;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
[TestFixture]
|
||||
public class GetCandidatesFixture : CoreTest<IdentificationService>
|
||||
{
|
||||
|
||||
private ArtistMetadata artist;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
artist = Builder<ArtistMetadata>
|
||||
.CreateNew()
|
||||
.With(x => x.Name = "artist")
|
||||
.Build();
|
||||
}
|
||||
|
||||
private List<Track> GivenTracks(int count)
|
||||
{
|
||||
return Builder<Track>
|
||||
.CreateListOfSize(count)
|
||||
.All()
|
||||
.With(x => x.ArtistMetadata = artist)
|
||||
.Build()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private ParsedTrackInfo GivenParsedTrackInfo(Track track, AlbumRelease release)
|
||||
{
|
||||
return Builder<ParsedTrackInfo>
|
||||
.CreateNew()
|
||||
.With(x => x.Title = track.Title)
|
||||
.With(x => x.AlbumTitle = release.Title)
|
||||
.With(x => x.Disambiguation = release.Disambiguation)
|
||||
.With(x => x.ReleaseMBId = release.ForeignReleaseId)
|
||||
.With(x => x.ArtistTitle = track.ArtistMetadata.Value.Name)
|
||||
.With(x => x.TrackNumbers = new[] { track.AbsoluteTrackNumber })
|
||||
.With(x => x.RecordingMBId = track.ForeignRecordingId)
|
||||
.With(x => x.Country = IsoCountries.Find("US"))
|
||||
.With(x => x.Label = release.Label.First())
|
||||
.With(x => x.Year = (uint)release.Album.Value.ReleaseDate.Value.Year)
|
||||
.Build();
|
||||
}
|
||||
|
||||
private List<LocalTrack> GivenLocalTracks(List<Track> tracks, AlbumRelease release)
|
||||
{
|
||||
var output = Builder<LocalTrack>
|
||||
.CreateListOfSize(tracks.Count)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
for (int i = 0; i < tracks.Count; i++)
|
||||
{
|
||||
output[i].FileTrackInfo = GivenParsedTrackInfo(tracks[i], release);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private AlbumRelease GivenAlbumRelease(string title, List<Track> tracks)
|
||||
{
|
||||
var album = Builder<Album>
|
||||
.CreateNew()
|
||||
.With(x => x.Title = title)
|
||||
.With(x => x.ArtistMetadata = artist)
|
||||
.Build();
|
||||
|
||||
var media = Builder<Medium>
|
||||
.CreateListOfSize(1)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
return Builder<AlbumRelease>
|
||||
.CreateNew()
|
||||
.With(x => x.Tracks = tracks)
|
||||
.With(x => x.Title = title)
|
||||
.With(x => x.Album = album)
|
||||
.With(x => x.Media = media)
|
||||
.With(x => x.Country = new List<string>())
|
||||
.With(x => x.Label = new List<string> { "label" })
|
||||
.With(x => x.ForeignReleaseId = null)
|
||||
.Build();
|
||||
}
|
||||
|
||||
private LocalAlbumRelease GivenLocalAlbumRelease()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
|
||||
return new LocalAlbumRelease(localTracks);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_candidates_by_fingerprint_should_not_fail_if_fingerprint_lookup_returned_null()
|
||||
{
|
||||
Mocker.GetMock<IFingerprintingService>()
|
||||
.Setup(x => x.Lookup(It.IsAny<List<LocalTrack>>(), It.IsAny<double>()))
|
||||
.Callback((List<LocalTrack> x, double thres) => {
|
||||
foreach(var track in x) {
|
||||
track.AcoustIdResults = null;
|
||||
}
|
||||
});
|
||||
|
||||
Mocker.GetMock<IReleaseService>()
|
||||
.Setup(x => x.GetReleasesByRecordingIds(It.IsAny<List<string>>()))
|
||||
.Returns(new List<AlbumRelease>());
|
||||
|
||||
var local = GivenLocalAlbumRelease();
|
||||
|
||||
Subject.GetCandidatesFromFingerprint(local).ShouldBeEquivalentTo(new List<AlbumRelease>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
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).ShouldBeEquivalentTo(new List<AlbumRelease> { release });
|
||||
}
|
||||
|
||||
[Test]
|
||||
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.GetReleasesByForeignReleaseId(new List<string>{ "xxx" }))
|
||||
.Returns(new List<AlbumRelease> { release });
|
||||
|
||||
Subject.GetCandidatesFromTags(localAlbumRelease, null, null, null).ShouldBeEquivalentTo(new List<AlbumRelease> { release });
|
||||
}
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
using FluentAssertions;
|
||||
using FluentValidation.Results;
|
||||
using Moq;
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.MetadataSource;
|
||||
using NzbDrone.Core.MetadataSource.SkyHook;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Music.Commands;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Profiles.Metadata;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
[TestFixture]
|
||||
public class IdentificationServiceFixture : DbTest
|
||||
{
|
||||
private ArtistService _artistService;
|
||||
private AddArtistService _addArtistService;
|
||||
private RefreshArtistService _refreshArtistService;
|
||||
|
||||
private IdentificationService Subject;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
UseRealHttp();
|
||||
|
||||
// Resolve all the parts we need
|
||||
Mocker.SetConstant<IArtistRepository>(Mocker.Resolve<ArtistRepository>());
|
||||
Mocker.SetConstant<IArtistMetadataRepository>(Mocker.Resolve<ArtistMetadataRepository>());
|
||||
Mocker.SetConstant<IAlbumRepository>(Mocker.Resolve<AlbumRepository>());
|
||||
Mocker.SetConstant<IReleaseRepository>(Mocker.Resolve<ReleaseRepository>());
|
||||
Mocker.SetConstant<ITrackRepository>(Mocker.Resolve<TrackRepository>());
|
||||
|
||||
Mocker.GetMock<IMetadataProfileService>().Setup(x => x.Exists(It.IsAny<int>())).Returns(true);
|
||||
|
||||
_artistService = Mocker.Resolve<ArtistService>();
|
||||
Mocker.SetConstant<IArtistService>(_artistService);
|
||||
Mocker.SetConstant<IAlbumService>(Mocker.Resolve<AlbumService>());
|
||||
Mocker.SetConstant<IReleaseService>(Mocker.Resolve<ReleaseService>());
|
||||
Mocker.SetConstant<ITrackService>(Mocker.Resolve<TrackService>());
|
||||
|
||||
Mocker.SetConstant<IConfigService>(Mocker.Resolve<IConfigService>());
|
||||
Mocker.SetConstant<IProvideArtistInfo>(Mocker.Resolve<SkyHookProxy>());
|
||||
Mocker.SetConstant<IProvideAlbumInfo>(Mocker.Resolve<SkyHookProxy>());
|
||||
|
||||
_addArtistService = Mocker.Resolve<AddArtistService>();
|
||||
|
||||
Mocker.SetConstant<IRefreshTrackService>(Mocker.Resolve<RefreshTrackService>());
|
||||
Mocker.SetConstant<IAddAlbumService>(Mocker.Resolve<AddAlbumService>());
|
||||
_refreshArtistService = Mocker.Resolve<RefreshArtistService>();
|
||||
|
||||
Mocker.GetMock<IAddArtistValidator>().Setup(x => x.Validate(It.IsAny<Artist>())).Returns(new ValidationResult());
|
||||
|
||||
Mocker.SetConstant<ITrackGroupingService>(Mocker.Resolve<TrackGroupingService>());
|
||||
Subject = Mocker.Resolve<IdentificationService>();
|
||||
|
||||
}
|
||||
|
||||
private void GivenMetadataProfile(MetadataProfile profile)
|
||||
{
|
||||
Mocker.GetMock<IMetadataProfileService>().Setup(x => x.Get(It.IsAny<int>())).Returns(profile);
|
||||
}
|
||||
|
||||
private Artist GivenArtist(string foreignArtistId)
|
||||
{
|
||||
var artist = _addArtistService.AddArtist(new Artist {
|
||||
Metadata = new ArtistMetadata {
|
||||
ForeignArtistId = foreignArtistId
|
||||
},
|
||||
Path = @"c:\test".AsOsAgnostic(),
|
||||
MetadataProfileId = 1
|
||||
});
|
||||
|
||||
var command = new RefreshArtistCommand{
|
||||
ArtistId = artist.Id,
|
||||
Trigger = CommandTrigger.Unspecified
|
||||
};
|
||||
|
||||
_refreshArtistService.Execute(command);
|
||||
|
||||
return _artistService.FindById(foreignArtistId);
|
||||
}
|
||||
|
||||
public static class IdTestCaseFactory
|
||||
{
|
||||
// for some reason using Directory.GetFiles causes nUnit to error
|
||||
private static string[] files = {
|
||||
"FilesWithMBIds.json",
|
||||
"PreferMissingToBadMatch.json",
|
||||
"InconsistentTyposInAlbum.json",
|
||||
"SucceedWhenManyAlbumsHaveSameTitle.json",
|
||||
"PenalizeUnknownMedia.json"
|
||||
};
|
||||
|
||||
public static IEnumerable TestCases
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
yield return new TestCaseData(file).SetName($"should_match_tracks_{file.Replace(".json", "")}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// these are slow to run so only do so manually
|
||||
[Explicit]
|
||||
[Test, TestCaseSource(typeof(IdTestCaseFactory), "TestCases")]
|
||||
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));
|
||||
|
||||
GivenMetadataProfile(testcase.MetadataProfile);
|
||||
|
||||
var artist = GivenArtist(testcase.Artist);
|
||||
|
||||
var tracks = testcase.Tracks.Select(x => new LocalTrack {
|
||||
Path = x.Path.AsOsAgnostic(),
|
||||
FileTrackInfo = x.FileTrackInfo
|
||||
}).ToList();
|
||||
|
||||
var result = Subject.Identify(tracks, artist, null, null, testcase.NewDownload, testcase.SingleRelease);
|
||||
|
||||
result.Should().HaveCount(testcase.ExpectedMusicBrainzReleaseIds.Count);
|
||||
result.Select(x => x.AlbumRelease.ForeignReleaseId).ShouldBeEquivalentTo(testcase.ExpectedMusicBrainzReleaseIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
|
||||
using NzbDrone.Test.Common;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
[TestFixture]
|
||||
public class MunkresFixture : TestBase
|
||||
{
|
||||
// 2d arrays don't play nicely with attributes
|
||||
public void RunTest(double[,] costMatrix, double expectedCost)
|
||||
{
|
||||
var m = new Munkres(costMatrix);
|
||||
m.Run();
|
||||
m.Cost.Should().Be(expectedCost);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MunkresSquareTest1()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{ 1, 2, 3 },
|
||||
{ 2, 4, 6 },
|
||||
{ 3, 6, 9 }
|
||||
};
|
||||
|
||||
RunTest(C, 10);
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MunkresSquareTest2()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{ 400, 150, 400 },
|
||||
{ 400, 450, 600 },
|
||||
{ 300, 225, 300 }
|
||||
};
|
||||
|
||||
RunTest(C, 850);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MunkresSquareTest3()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{ 10, 10, 8 },
|
||||
{ 9, 8, 1 },
|
||||
{ 9, 7, 4 }
|
||||
};
|
||||
|
||||
RunTest(C, 18);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MunkresSquareTest4()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{ 5, 9, 1 },
|
||||
{ 10, 3, 2 },
|
||||
{ 8, 7, 4 }
|
||||
};
|
||||
|
||||
RunTest(C, 12);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MunkresSquareTest5()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{12, 26, 17, 0, 0},
|
||||
{49, 43, 36, 10, 5},
|
||||
{97, 9, 66, 34, 0},
|
||||
{52, 42, 19, 36, 0},
|
||||
{15, 93, 55, 80, 0}
|
||||
};
|
||||
|
||||
RunTest(C, 48);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Munkres5x5Test()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{12, 9, 27, 10, 23},
|
||||
{7, 13, 13, 30, 19},
|
||||
{25, 18, 26, 11, 26},
|
||||
{9, 28, 26, 23, 13},
|
||||
{16, 16, 24, 6, 9}
|
||||
};
|
||||
|
||||
RunTest(C, 51);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Munkres10x10Test()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{37, 34, 29, 26, 19, 8, 9, 23, 19, 29},
|
||||
{9, 28, 20, 8, 18, 20, 14, 33, 23, 14},
|
||||
{15, 26, 12, 28, 6, 17, 9, 13, 21, 7},
|
||||
{2, 8, 38, 36, 39, 5, 36, 2, 38, 27},
|
||||
{30, 3, 33, 16, 21, 39, 7, 23, 28, 36},
|
||||
{7, 5, 19, 22, 36, 36, 24, 19, 30, 2},
|
||||
{34, 20, 13, 36, 12, 33, 9, 10, 23, 5},
|
||||
{7, 37, 22, 39, 33, 39, 10, 3, 13, 26},
|
||||
{21, 25, 23, 39, 31, 37, 32, 33, 38, 1},
|
||||
{17, 34, 40, 10, 29, 37, 40, 3, 25, 3}
|
||||
};
|
||||
|
||||
RunTest(C, 66);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Munkres20x20Test()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{5, 4, 3, 9, 8, 9, 3, 5, 6, 9, 4, 10, 3, 5, 6, 6, 1, 8, 10, 2},
|
||||
{10, 9, 9, 2, 8, 3, 9, 9, 10, 1, 7, 10, 8, 4, 2, 1, 4, 8, 4, 8},
|
||||
{10, 4, 4, 3, 1, 3, 5, 10, 6, 8, 6, 8, 4, 10, 7, 2, 4, 5, 1, 8},
|
||||
{2, 1, 4, 2, 3, 9, 3, 4, 7, 3, 4, 1, 3, 2, 9, 8, 6, 5, 7, 8},
|
||||
{3, 4, 4, 1, 4, 10, 1, 2, 6, 4, 5, 10, 2, 2, 3, 9, 10, 9, 9, 10},
|
||||
{1, 10, 1, 8, 1, 3, 1, 7, 1, 1, 2, 1, 2, 6, 3, 3, 4, 4, 8, 6},
|
||||
{1, 8, 7, 10, 10, 3, 4, 6, 1, 6, 6, 4, 9, 6, 9, 6, 4, 5, 4, 7},
|
||||
{8, 10, 3, 9, 4, 9, 3, 3, 4, 6, 4, 2, 6, 7, 7, 4, 4, 3, 4, 7},
|
||||
{1, 3, 8, 2, 6, 9, 2, 7, 4, 8, 10, 8, 10, 5, 1, 3, 10, 10, 2, 9},
|
||||
{2, 4, 1, 9, 2, 9, 7, 8, 2, 1, 4, 10, 5, 2, 7, 6, 5, 7, 2, 6},
|
||||
{4, 5, 1, 4, 2, 3, 3, 4, 1, 8, 8, 2, 6, 9, 5, 9, 6, 3, 9, 3},
|
||||
{3, 1, 1, 8, 6, 8, 8, 7, 9, 3, 2, 1, 8, 2, 4, 7, 3, 1, 2, 4},
|
||||
{5, 9, 8, 6, 10, 4, 10, 3, 4, 10, 10, 10, 1, 7, 8, 8, 7, 7, 8, 8},
|
||||
{1, 4, 6, 1, 6, 1, 2, 10, 5, 10, 2, 6, 2, 4, 5, 5, 3, 5, 1, 5},
|
||||
{5, 6, 9, 10, 6, 6, 10, 6, 4, 1, 5, 3, 9, 5, 2, 10, 9, 9, 5, 1},
|
||||
{10, 9, 4, 6, 9, 5, 3, 7, 10, 1, 6, 8, 1, 1, 10, 9, 5, 7, 7, 5},
|
||||
{2, 6, 6, 6, 6, 2, 9, 4, 7, 5, 3, 2, 10, 3, 4, 5, 10, 9, 1, 7},
|
||||
{5, 2, 4, 9, 8, 4, 8, 2, 4, 1, 3, 7, 6, 8, 1, 6, 8, 8, 10, 10},
|
||||
{9, 6, 3, 1, 8, 5, 7, 8, 7, 2, 1, 8, 2, 8, 3, 7, 4, 8, 7, 7},
|
||||
{8, 4, 4, 9, 7, 10, 6, 2, 1, 5, 8, 5, 1, 1, 1, 9, 1, 3, 5, 3}
|
||||
};
|
||||
|
||||
RunTest(C, 22);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MunkresRectangularTest1()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{ 400, 150, 400, 1 },
|
||||
{ 400, 450, 600, 2 },
|
||||
{ 300, 225, 300, 3 }
|
||||
};
|
||||
|
||||
RunTest(C, 452);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MunkresRectangularTest2()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{ 10, 10, 8, 11 },
|
||||
{ 9, 8, 1, 1 },
|
||||
{ 9, 7, 4, 10 }
|
||||
};
|
||||
|
||||
RunTest(C, 15);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MunkresRectangularTest3()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{34, 26, 17, 12},
|
||||
{43, 43, 36, 10},
|
||||
{97, 47, 66, 34},
|
||||
{52, 42, 19, 36},
|
||||
{15, 93, 55, 80}
|
||||
};
|
||||
|
||||
RunTest(C, 70);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
|
||||
using FluentAssertions;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using FizzWare.NBuilder;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Parser;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
[TestFixture]
|
||||
public class TrackDistanceFixture : CoreTest<IdentificationService>
|
||||
{
|
||||
private Track GivenTrack(string title)
|
||||
{
|
||||
var artist = Builder<ArtistMetadata>
|
||||
.CreateNew()
|
||||
.With(x => x.Name = "artist")
|
||||
.Build();
|
||||
|
||||
var mbTrack = Builder<Track>
|
||||
.CreateNew()
|
||||
.With(x => x.Title = title)
|
||||
.With(x => x.ArtistMetadata = artist)
|
||||
.Build();
|
||||
|
||||
return mbTrack;
|
||||
}
|
||||
|
||||
private LocalTrack GivenLocalTrack(Track track)
|
||||
{
|
||||
var fileInfo = Builder<ParsedTrackInfo>
|
||||
.CreateNew()
|
||||
.With(x => x.Title = track.Title)
|
||||
.With(x => x.CleanTitle = track.Title.CleanTrackTitle())
|
||||
.With(x => x.ArtistTitle = track.ArtistMetadata.Value.Name)
|
||||
.With(x => x.TrackNumbers = new[] { 1 })
|
||||
.With(x => x.RecordingMBId = track.ForeignRecordingId)
|
||||
.Build();
|
||||
|
||||
var localTrack = Builder<LocalTrack>
|
||||
.CreateNew()
|
||||
.With(x => x.FileTrackInfo = fileInfo)
|
||||
.Build();
|
||||
|
||||
return localTrack;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_identical_tracks()
|
||||
{
|
||||
var track = GivenTrack("one");
|
||||
var localTrack = GivenLocalTrack(track);
|
||||
|
||||
Subject.TrackDistance(localTrack, track, 1, true).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_feat_removed_from_localtrack()
|
||||
{
|
||||
var track = GivenTrack("one");
|
||||
var localTrack = GivenLocalTrack(track);
|
||||
localTrack.FileTrackInfo.Title = "one (feat. two)";
|
||||
|
||||
Subject.TrackDistance(localTrack, track, 1, true).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_different_title()
|
||||
{
|
||||
var track = GivenTrack("one");
|
||||
var localTrack = GivenLocalTrack(track);
|
||||
localTrack.FileTrackInfo.CleanTitle = "foo";
|
||||
|
||||
Subject.TrackDistance(localTrack, track, 1, true).NormalizedDistance().Should().NotBe(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_different_artist()
|
||||
{
|
||||
var track = GivenTrack("one");
|
||||
var localTrack = GivenLocalTrack(track);
|
||||
localTrack.FileTrackInfo.ArtistTitle = "foo";
|
||||
|
||||
Subject.TrackDistance(localTrack, track, 1, true).NormalizedDistance().Should().NotBe(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_various_artists_tolerated()
|
||||
{
|
||||
var track = GivenTrack("one");
|
||||
var localTrack = GivenLocalTrack(track);
|
||||
localTrack.FileTrackInfo.ArtistTitle = "Various Artists";
|
||||
|
||||
Subject.TrackDistance(localTrack, track, 1, true).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
+369
@@ -0,0 +1,369 @@
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
|
||||
using NzbDrone.Test.Common;
|
||||
using FluentAssertions;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using FizzWare.NBuilder;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using FizzWare.NBuilder.PropertyNaming;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
// we need to use random strings to test the va (so we don't just get artist1, artist2 etc which are too similar)
|
||||
// but the standard random value namer would give paths that are too long on windows
|
||||
public class RandomValueNamerShortStrings : RandomValuePropertyNamer
|
||||
{
|
||||
private readonly IRandomGenerator generator;
|
||||
private static readonly List<char> allowedChars;
|
||||
|
||||
public RandomValueNamerShortStrings(BuilderSettings settings) : base(settings)
|
||||
{
|
||||
generator = new RandomGenerator();
|
||||
}
|
||||
|
||||
static RandomValueNamerShortStrings()
|
||||
{
|
||||
allowedChars = new List<char>();
|
||||
for (char c = 'a'; c < 'z'; c++)
|
||||
{
|
||||
allowedChars.Add(c);
|
||||
}
|
||||
|
||||
for (char c = 'A'; c < 'Z'; c++)
|
||||
{
|
||||
allowedChars.Add(c);
|
||||
}
|
||||
|
||||
for (char c = '0'; c < '9'; c++)
|
||||
{
|
||||
allowedChars.Add(c);
|
||||
}
|
||||
}
|
||||
|
||||
protected override string GetString(MemberInfo memberInfo)
|
||||
{
|
||||
int length = generator.Next(0, 100);
|
||||
|
||||
char[] chars = new char[length];
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
int index = generator.Next(0, allowedChars.Count - 1);
|
||||
chars[i] = allowedChars[index];
|
||||
}
|
||||
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(chars);
|
||||
return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
|
||||
}
|
||||
}
|
||||
|
||||
[TestFixture]
|
||||
public class TrackGroupingServiceFixture : CoreTest<TrackGroupingService>
|
||||
{
|
||||
private List<LocalTrack> GivenTracks(string root, string artist, string album, int count)
|
||||
{
|
||||
var fileInfos = Builder<ParsedTrackInfo>
|
||||
.CreateListOfSize(count)
|
||||
.All()
|
||||
.With(f => f.ArtistTitle = artist)
|
||||
.With(f => f.AlbumTitle = album)
|
||||
.With(f => f.AlbumMBId = null)
|
||||
.With(f => f.ReleaseMBId = null)
|
||||
.Build();
|
||||
|
||||
var tracks = fileInfos.Select(x => Builder<LocalTrack>
|
||||
.CreateNew()
|
||||
.With(y => y.FileTrackInfo = x)
|
||||
.With(y => y.Path = Path.Combine(root, x.Title))
|
||||
.Build()).ToList();
|
||||
|
||||
return tracks;
|
||||
}
|
||||
|
||||
private List<LocalTrack> GivenTracksWithNoTags(string root, int count)
|
||||
{
|
||||
var outp = new List<LocalTrack>();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var track = Builder<LocalTrack>
|
||||
.CreateNew()
|
||||
.With(y => y.FileTrackInfo = new ParsedTrackInfo())
|
||||
.With(y => y.Path = Path.Combine(root, $"{i}.mp3"))
|
||||
.Build();
|
||||
outp.Add(track);
|
||||
}
|
||||
|
||||
return outp;
|
||||
}
|
||||
|
||||
private List<LocalTrack> GivenVaTracks(string root, string album, int count)
|
||||
{
|
||||
var settings = new BuilderSettings();
|
||||
settings.SetPropertyNamerFor<ParsedTrackInfo>(new RandomValueNamerShortStrings(settings));
|
||||
|
||||
var builder = new Builder(settings);
|
||||
|
||||
var fileInfos = builder
|
||||
.CreateListOfSize<ParsedTrackInfo>(count)
|
||||
.All()
|
||||
.With(f => f.AlbumTitle = "album")
|
||||
.With(f => f.AlbumMBId = null)
|
||||
.With(f => f.ReleaseMBId = null)
|
||||
.Build();
|
||||
|
||||
var tracks = fileInfos.Select(x => Builder<LocalTrack>
|
||||
.CreateNew()
|
||||
.With(y => y.FileTrackInfo = x)
|
||||
.With(y => y.Path = Path.Combine(@"C:\music\incoming".AsOsAgnostic(), x.Title))
|
||||
.Build()).ToList();
|
||||
|
||||
return tracks;
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(10)]
|
||||
public void single_artist_is_not_various_artists(int count)
|
||||
{
|
||||
var tracks = GivenTracks(@"C:\music\incoming".AsOsAgnostic(), "artist", "album", count);
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void all_different_artists_is_various_artists()
|
||||
{
|
||||
var tracks = GivenVaTracks(@"C:\music\incoming".AsOsAgnostic(), "album", 10);
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void two_artists_is_not_various_artists()
|
||||
{
|
||||
var dir = @"C:\music\incoming".AsOsAgnostic();
|
||||
var tracks = GivenTracks(dir, "artist1", "album", 10);
|
||||
tracks.AddRange(GivenTracks(dir, "artist2", "album", 10));
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void mostly_different_artists_is_various_artists()
|
||||
{
|
||||
var dir = @"C:\music\incoming".AsOsAgnostic();
|
||||
var tracks = GivenVaTracks(dir, "album", 10);
|
||||
tracks.AddRange(GivenTracks(dir, "single_artist", "album", 2));
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(true);
|
||||
}
|
||||
|
||||
[TestCase("")]
|
||||
[TestCase("Various Artists")]
|
||||
[TestCase("Various")]
|
||||
[TestCase("VA")]
|
||||
[TestCase("Unknown")]
|
||||
public void va_artist_title_is_various_artists(string artist)
|
||||
{
|
||||
var tracks = GivenTracks(@"C:\music\incoming".AsOsAgnostic(), artist, "album", 10);
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(true);
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(10)]
|
||||
public void should_group_single_artist_album(int count)
|
||||
{
|
||||
var tracks = GivenTracks(@"C:\music\incoming".AsOsAgnostic(), "artist", "album", count);
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(true);
|
||||
|
||||
output.Count.Should().Be(1);
|
||||
output[0].LocalTracks.Count.Should().Be(count);
|
||||
}
|
||||
|
||||
[TestCase("cd")]
|
||||
[TestCase("disc")]
|
||||
[TestCase("disk")]
|
||||
public void should_group_multi_disc_release(string mediaName)
|
||||
{
|
||||
var tracks = GivenTracks($"C:\\music\\incoming\\artist - album\\{mediaName} 1".AsOsAgnostic(),
|
||||
"artist", "album", 10);
|
||||
tracks.AddRange(GivenTracks($"C:\\music\\incoming\\artist - album\\{mediaName} 2".AsOsAgnostic(),
|
||||
"artist", "album", 5));
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(true);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
output.Count.Should().Be(1);
|
||||
output[0].LocalTracks.Count.Should().Be(15);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_group_two_different_albums_by_same_artist()
|
||||
{
|
||||
var tracks = GivenTracks($"C:\\music\\incoming\\artist - album1".AsOsAgnostic(),
|
||||
"artist", "album1", 10);
|
||||
tracks.AddRange(GivenTracks($"C:\\music\\incoming\\artist - album2".AsOsAgnostic(),
|
||||
"artist", "album2", 5));
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(false);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
output.Count.Should().Be(2);
|
||||
output[0].LocalTracks.Count.Should().Be(10);
|
||||
output[1].LocalTracks.Count.Should().Be(5);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_group_albums_with_typos()
|
||||
{
|
||||
var tracks = GivenTracks($"C:\\music\\incoming\\artist - album".AsOsAgnostic(),
|
||||
"artist", "Rastaman Vibration (Remastered)", 10);
|
||||
tracks.AddRange(GivenTracks($"C:\\music\\incoming\\artist - album".AsOsAgnostic(),
|
||||
"artist", "Rastaman Vibration (Remastered", 5));
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(true);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
output.Count.Should().Be(1);
|
||||
output[0].LocalTracks.Count.Should().Be(15);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_group_two_different_tracks_in_same_directory()
|
||||
{
|
||||
var tracks = GivenTracks($"C:\\music\\incoming".AsOsAgnostic(),
|
||||
"artist", "album1", 1);
|
||||
tracks.AddRange(GivenTracks($"C:\\music\\incoming".AsOsAgnostic(),
|
||||
"artist", "album2", 1));
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(false);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
output.Count.Should().Be(2);
|
||||
output[0].LocalTracks.Count.Should().Be(1);
|
||||
output[1].LocalTracks.Count.Should().Be(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_separate_two_albums_in_same_directory()
|
||||
{
|
||||
var tracks = GivenTracks($"C:\\music\\incoming\\artist discog".AsOsAgnostic(),
|
||||
"artist", "album1", 10);
|
||||
tracks.AddRange(GivenTracks($"C:\\music\\incoming\\artist disog".AsOsAgnostic(),
|
||||
"artist", "album2", 5));
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(false);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
output.Count.Should().Be(2);
|
||||
output[0].LocalTracks.Count.Should().Be(10);
|
||||
output[1].LocalTracks.Count.Should().Be(5);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_separate_many_albums_in_same_directory()
|
||||
{
|
||||
var tracks = new List<LocalTrack>();
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
tracks.AddRange(GivenTracks($"C:\\music".AsOsAgnostic(),
|
||||
"artist" + i, "album" + i, 10));
|
||||
}
|
||||
|
||||
// don't test various artists here because it's designed to only work if there's a common album
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(false);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
output.Count.Should().Be(100);
|
||||
output.Select(x => x.LocalTracks.Count).Distinct().ShouldBeEquivalentTo(new List<int> { 10 });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_separate_two_albums_by_different_artists_in_same_directory()
|
||||
{
|
||||
var tracks = GivenTracks($"C:\\music\\incoming".AsOsAgnostic(),
|
||||
"artist1", "album1", 10);
|
||||
tracks.AddRange(GivenTracks($"C:\\music\\incoming".AsOsAgnostic(),
|
||||
"artist2", "album2", 5));
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(false);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
output.Count.Should().Be(2);
|
||||
output[0].LocalTracks.Count.Should().Be(10);
|
||||
output[1].LocalTracks.Count.Should().Be(5);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_group_va_release()
|
||||
{
|
||||
var tracks = GivenVaTracks(@"C:\music\incoming".AsOsAgnostic(), "album", 10);
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(true);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(true);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
output.Count.Should().Be(1);
|
||||
output[0].LocalTracks.Count.Should().Be(10);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_group_two_albums_by_different_artists_with_same_title()
|
||||
{
|
||||
var tracks = GivenTracks($"C:\\music\\incoming\\album".AsOsAgnostic(),
|
||||
"artist1", "album", 10);
|
||||
tracks.AddRange(GivenTracks($"C:\\music\\incoming\\album".AsOsAgnostic(),
|
||||
"artist2", "album", 5));
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(false);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
|
||||
output.Count.Should().Be(2);
|
||||
output[0].LocalTracks.Count.Should().Be(10);
|
||||
output[1].LocalTracks.Count.Should().Be(5);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_fail_if_all_tags_null()
|
||||
{
|
||||
var tracks = GivenTracksWithNoTags($"C:\\music\\incoming\\album".AsOsAgnostic(), 10);
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(true);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
output.Count.Should().Be(1);
|
||||
output[0].LocalTracks.Count.Should().Be(10);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_fail_if_some_tags_null()
|
||||
{
|
||||
var tracks = GivenTracks($"C:\\music\\incoming\\album".AsOsAgnostic(),
|
||||
"artist1", "album", 10);
|
||||
tracks.AddRange(GivenTracksWithNoTags($"C:\\music\\incoming\\album".AsOsAgnostic(), 2));
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(true);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
output.Count.Should().Be(1);
|
||||
output[0].LocalTracks.Count.Should().Be(12);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
|
||||
using FluentAssertions;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using FizzWare.NBuilder;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Music;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Common.Serializer;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
[TestFixture]
|
||||
public class TrackMappingFixture : CoreTest<IdentificationService>
|
||||
{
|
||||
|
||||
private ArtistMetadata artist;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
artist = Builder<ArtistMetadata>
|
||||
.CreateNew()
|
||||
.With(x => x.Name = "artist")
|
||||
.Build();
|
||||
}
|
||||
|
||||
private List<Track> GivenTracks(int count)
|
||||
{
|
||||
return Builder<Track>
|
||||
.CreateListOfSize(count)
|
||||
.All()
|
||||
.With(x => x.ArtistMetadata = artist)
|
||||
.Build()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private ParsedTrackInfo GivenParsedTrackInfo(Track track, AlbumRelease release)
|
||||
{
|
||||
return Builder<ParsedTrackInfo>
|
||||
.CreateNew()
|
||||
.With(x => x.Title = track.Title)
|
||||
.With(x => x.CleanTitle = track.Title.CleanTrackTitle())
|
||||
.With(x => x.AlbumTitle = release.Title)
|
||||
.With(x => x.Disambiguation = release.Disambiguation)
|
||||
.With(x => x.ReleaseMBId = release.ForeignReleaseId)
|
||||
.With(x => x.ArtistTitle = track.ArtistMetadata.Value.Name)
|
||||
.With(x => x.TrackNumbers = new[] { track.AbsoluteTrackNumber })
|
||||
.With(x => x.RecordingMBId = track.ForeignRecordingId)
|
||||
.With(x => x.Country = IsoCountries.Find("US"))
|
||||
.With(x => x.Label = release.Label.First())
|
||||
.With(x => x.Year = (uint)release.Album.Value.ReleaseDate.Value.Year)
|
||||
.Build();
|
||||
}
|
||||
|
||||
private List<LocalTrack> GivenLocalTracks(List<Track> tracks, AlbumRelease release)
|
||||
{
|
||||
var output = Builder<LocalTrack>
|
||||
.CreateListOfSize(tracks.Count)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
for (int i = 0; i < tracks.Count; i++)
|
||||
{
|
||||
output[i].FileTrackInfo = GivenParsedTrackInfo(tracks[i], release);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private AlbumRelease GivenAlbumRelease(string title, List<Track> tracks)
|
||||
{
|
||||
var album = Builder<Album>
|
||||
.CreateNew()
|
||||
.With(x => x.Title = title)
|
||||
.With(x => x.ArtistMetadata = artist)
|
||||
.Build();
|
||||
|
||||
var media = Builder<Medium>
|
||||
.CreateListOfSize(1)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
return Builder<AlbumRelease>
|
||||
.CreateNew()
|
||||
.With(x => x.Tracks = tracks)
|
||||
.With(x => x.Title = title)
|
||||
.With(x => x.Album = album)
|
||||
.With(x => x.Media = media)
|
||||
.With(x => x.Country = new List<string>())
|
||||
.With(x => x.Label = new List<string> { "label" })
|
||||
.Build();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_reorder_when_track_numbers_incorrect()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
|
||||
localTracks[2].FileTrackInfo.TrackNumbers = new [] { 2 };
|
||||
localTracks[1].FileTrackInfo.TrackNumbers = new [] { 3 };
|
||||
localTracks = new [] {0, 2, 1}.Select(x => localTracks[x]).ToList();
|
||||
|
||||
var result = Subject.MapReleaseTracks(localTracks, tracks);
|
||||
|
||||
result.Mapping
|
||||
.ToDictionary(x => x.Key, y => y.Value.Item1)
|
||||
.ShouldBeEquivalentTo(new Dictionary<LocalTrack, Track> {
|
||||
{localTracks[0], tracks[0]},
|
||||
{localTracks[1], tracks[2]},
|
||||
{localTracks[2], tracks[1]},
|
||||
});
|
||||
result.LocalExtra.Should().BeEmpty();
|
||||
result.MBExtra.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_order_works_with_invalid_track_numbers()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
|
||||
foreach (var track in localTracks)
|
||||
{
|
||||
track.FileTrackInfo.TrackNumbers = new[] { 1 };
|
||||
}
|
||||
|
||||
var result = Subject.MapReleaseTracks(localTracks, tracks);
|
||||
|
||||
result.Mapping
|
||||
.ToDictionary(x => x.Key, y => y.Value.Item1)
|
||||
.ShouldBeEquivalentTo(new Dictionary<LocalTrack, Track> {
|
||||
{localTracks[0], tracks[0]},
|
||||
{localTracks[1], tracks[1]},
|
||||
{localTracks[2], tracks[2]},
|
||||
});
|
||||
result.LocalExtra.Should().BeEmpty();
|
||||
result.MBExtra.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_order_works_with_missing_tracks()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
localTracks.RemoveAt(1);
|
||||
|
||||
var result = Subject.MapReleaseTracks(localTracks, tracks);
|
||||
|
||||
result.Mapping
|
||||
.ToDictionary(x => x.Key, y => y.Value.Item1)
|
||||
.ShouldBeEquivalentTo(new Dictionary<LocalTrack, Track> {
|
||||
{localTracks[0], tracks[0]},
|
||||
{localTracks[1], tracks[2]}
|
||||
});
|
||||
result.LocalExtra.Should().BeEmpty();
|
||||
result.MBExtra.ShouldBeEquivalentTo(new List<Track> { tracks[1] });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_order_works_with_extra_tracks()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
tracks.RemoveAt(1);
|
||||
|
||||
var result = Subject.MapReleaseTracks(localTracks, tracks);
|
||||
|
||||
result.Mapping
|
||||
.ToDictionary(x => x.Key, y => y.Value.Item1)
|
||||
.ShouldBeEquivalentTo(new Dictionary<LocalTrack, Track> {
|
||||
{localTracks[0], tracks[0]},
|
||||
{localTracks[2], tracks[1]}
|
||||
});
|
||||
result.LocalExtra.ShouldBeEquivalentTo(new List<LocalTrack> { localTracks[1] });
|
||||
result.MBExtra.Should().BeEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,16 +6,18 @@ using NUnit.Framework;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Profiles.Qualities;
|
||||
using NzbDrone.Core.Profiles;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Test.Common;
|
||||
using FizzWare.NBuilder;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Aggregation;
|
||||
using NzbDrone.Core.Profiles.Qualities;
|
||||
using NzbDrone.Core.Profiles.Languages;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
||||
{
|
||||
@@ -25,27 +27,54 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
||||
private List<string> _audioFiles;
|
||||
private LocalTrack _localTrack;
|
||||
private Artist _artist;
|
||||
private AlbumRelease _albumRelease;
|
||||
private QualityModel _quality;
|
||||
|
||||
private Mock<IImportDecisionEngineSpecification> _pass1;
|
||||
private Mock<IImportDecisionEngineSpecification> _pass2;
|
||||
private Mock<IImportDecisionEngineSpecification> _pass3;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>> _albumpass1;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>> _albumpass2;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>> _albumpass3;
|
||||
|
||||
private Mock<IImportDecisionEngineSpecification> _fail1;
|
||||
private Mock<IImportDecisionEngineSpecification> _fail2;
|
||||
private Mock<IImportDecisionEngineSpecification> _fail3;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>> _albumfail1;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>> _albumfail2;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>> _albumfail3;
|
||||
|
||||
|
||||
private Mock<IImportDecisionEngineSpecification<LocalTrack>> _pass1;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalTrack>> _pass2;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalTrack>> _pass3;
|
||||
|
||||
private Mock<IImportDecisionEngineSpecification<LocalTrack>> _fail1;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalTrack>> _fail2;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalTrack>> _fail3;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_pass1 = new Mock<IImportDecisionEngineSpecification>();
|
||||
_pass2 = new Mock<IImportDecisionEngineSpecification>();
|
||||
_pass3 = new Mock<IImportDecisionEngineSpecification>();
|
||||
_albumpass1 = new Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>>();
|
||||
_albumpass2 = new Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>>();
|
||||
_albumpass3 = new Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>>();
|
||||
|
||||
_fail1 = new Mock<IImportDecisionEngineSpecification>();
|
||||
_fail2 = new Mock<IImportDecisionEngineSpecification>();
|
||||
_fail3 = new Mock<IImportDecisionEngineSpecification>();
|
||||
_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());
|
||||
@@ -59,25 +88,33 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
||||
.With(e => e.LanguageProfile = new LanguageProfile { Languages = Languages.LanguageFixture.GetDefaultLanguages() })
|
||||
.Build();
|
||||
|
||||
_albumRelease = Builder<AlbumRelease>.CreateNew()
|
||||
.Build();
|
||||
|
||||
_quality = new QualityModel(Quality.MP3_256);
|
||||
|
||||
_localTrack = new LocalTrack
|
||||
{
|
||||
{
|
||||
Artist = _artist,
|
||||
Quality = _quality,
|
||||
Language = Language.Spanish,
|
||||
Tracks = new List<Track> { new Track() },
|
||||
Path = @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.Spanish.XviD-OSiTV.avi"
|
||||
Path = @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi"
|
||||
};
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.IsAny<ParsedTrackInfo>()))
|
||||
.Returns(_localTrack);
|
||||
GivenVideoFiles(new List<string> { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi".AsOsAgnostic() });
|
||||
|
||||
GivenVideoFiles(new List<string> { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.Spanish.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>()))
|
||||
.Returns((List<LocalTrack> tracks, Artist artist, Album album, AlbumRelease release, bool newDownload, bool singleRelease) => {
|
||||
var ret = new LocalAlbumRelease(tracks);
|
||||
ret.AlbumRelease = _albumRelease;
|
||||
return new List<LocalAlbumRelease> { ret };
|
||||
});
|
||||
|
||||
GivenSpecifications(_albumpass1);
|
||||
}
|
||||
|
||||
private void GivenSpecifications(params Mock<IImportDecisionEngineSpecification>[] mocks)
|
||||
private void GivenSpecifications<T>(params Mock<IImportDecisionEngineSpecification<T>>[] mocks)
|
||||
{
|
||||
Mocker.SetConstant(mocks.Select(c => c.Object));
|
||||
}
|
||||
@@ -91,24 +128,83 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
||||
.Returns(_audioFiles);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_call_all_specifications()
|
||||
private void GivenAugmentationSuccess()
|
||||
{
|
||||
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
|
||||
|
||||
Subject.GetImportDecisions(_audioFiles, new Artist(), null);
|
||||
|
||||
_fail1.Verify(c => c.IsSatisfiedBy(_localTrack), Times.Once());
|
||||
_fail2.Verify(c => c.IsSatisfiedBy(_localTrack), Times.Once());
|
||||
_fail3.Verify(c => c.IsSatisfiedBy(_localTrack), Times.Once());
|
||||
_pass1.Verify(c => c.IsSatisfiedBy(_localTrack), Times.Once());
|
||||
_pass2.Verify(c => c.IsSatisfiedBy(_localTrack), Times.Once());
|
||||
_pass3.Verify(c => c.IsSatisfiedBy(_localTrack), Times.Once());
|
||||
Mocker.GetMock<IAugmentingService>()
|
||||
.Setup(s => s.Augment(It.IsAny<LocalTrack>(), It.IsAny<bool>()))
|
||||
.Callback<LocalTrack, bool>((localTrack, otherFiles) =>
|
||||
{
|
||||
localTrack.Tracks = _localTrack.Tracks;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_rejected_if_single_specs_fail()
|
||||
public void should_call_all_album_specifications()
|
||||
{
|
||||
var downloadClientItem = Builder<DownloadClientItem>.CreateNew().Build();
|
||||
GivenAugmentationSuccess();
|
||||
GivenSpecifications(_albumpass1, _albumpass2, _albumpass3, _albumfail1, _albumfail2, _albumfail3);
|
||||
|
||||
Subject.GetImportDecisions(_audioFiles, new Artist(), null, downloadClientItem, null, false, false, false);
|
||||
|
||||
_albumfail1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>()), Times.Once());
|
||||
_albumfail2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>()), Times.Once());
|
||||
_albumfail3.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>()), Times.Once());
|
||||
_albumpass1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>()), Times.Once());
|
||||
_albumpass2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>()), Times.Once());
|
||||
_albumpass3.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_call_all_track_specifications_if_album_accepted()
|
||||
{
|
||||
var downloadClientItem = Builder<DownloadClientItem>.CreateNew().Build();
|
||||
GivenAugmentationSuccess();
|
||||
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
|
||||
|
||||
Subject.GetImportDecisions(_audioFiles, new Artist(), null, downloadClientItem, null, false, false, false);
|
||||
|
||||
_fail1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>()), Times.Once());
|
||||
_fail2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>()), Times.Once());
|
||||
_fail3.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>()), Times.Once());
|
||||
_pass1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>()), Times.Once());
|
||||
_pass2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>()), Times.Once());
|
||||
_pass3.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_call_no_track_specifications_if_album_rejected()
|
||||
{
|
||||
var downloadClientItem = Builder<DownloadClientItem>.CreateNew().Build();
|
||||
GivenAugmentationSuccess();
|
||||
GivenSpecifications(_albumpass1, _albumpass2, _albumpass3, _albumfail1, _albumfail2, _albumfail3);
|
||||
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
|
||||
|
||||
Subject.GetImportDecisions(_audioFiles, new Artist(), null, downloadClientItem, null, false, false, false);
|
||||
|
||||
_fail1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>()), Times.Never());
|
||||
_fail2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>()), Times.Never());
|
||||
_fail3.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>()), Times.Never());
|
||||
_pass1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>()), Times.Never());
|
||||
_pass2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>()), Times.Never());
|
||||
_pass3.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_rejected_if_only_album_spec_fails()
|
||||
{
|
||||
GivenSpecifications(_albumfail1);
|
||||
GivenSpecifications(_pass1);
|
||||
|
||||
var result = Subject.GetImportDecisions(_audioFiles, new Artist());
|
||||
|
||||
result.Single().Approved.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_rejected_if_only_track_spec_fails()
|
||||
{
|
||||
GivenSpecifications(_albumpass1);
|
||||
GivenSpecifications(_fail1);
|
||||
|
||||
var result = Subject.GetImportDecisions(_audioFiles, new Artist());
|
||||
@@ -117,8 +213,20 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_rejected_if_one_of_specs_fail()
|
||||
public void should_return_rejected_if_one_album_spec_fails()
|
||||
{
|
||||
GivenSpecifications(_albumpass1, _albumfail1, _albumpass2, _albumpass3);
|
||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||
|
||||
var result = Subject.GetImportDecisions(_audioFiles, new Artist());
|
||||
|
||||
result.Single().Approved.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_rejected_if_one_track_spec_fails()
|
||||
{
|
||||
GivenSpecifications(_albumpass1, _albumpass2, _albumpass3);
|
||||
GivenSpecifications(_pass1, _fail1, _pass2, _pass3);
|
||||
|
||||
var result = Subject.GetImportDecisions(_audioFiles, new Artist());
|
||||
@@ -127,8 +235,10 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_pass_if_all_specs_pass()
|
||||
public void should_return_approved_if_all_specs_pass()
|
||||
{
|
||||
GivenAugmentationSuccess();
|
||||
GivenSpecifications(_albumpass1, _albumpass2, _albumpass3);
|
||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||
|
||||
var result = Subject.GetImportDecisions(_audioFiles, new Artist());
|
||||
@@ -139,6 +249,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
||||
[Test]
|
||||
public void should_have_same_number_of_rejections_as_specs_that_failed()
|
||||
{
|
||||
GivenAugmentationSuccess();
|
||||
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
|
||||
|
||||
var result = Subject.GetImportDecisions(_audioFiles, new Artist());
|
||||
@@ -146,12 +257,12 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_blowup_the_process_due_to_failed_parse()
|
||||
public void should_not_blowup_the_process_due_to_failed_augment()
|
||||
{
|
||||
GivenSpecifications(_pass1);
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.IsAny<ParsedTrackInfo>()))
|
||||
Mocker.GetMock<IAugmentingService>()
|
||||
.Setup(c => c.Augment(It.IsAny<LocalTrack>(), It.IsAny<bool>()))
|
||||
.Throws<TestException>();
|
||||
|
||||
_audioFiles = new List<string>
|
||||
@@ -165,103 +276,45 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
||||
|
||||
Subject.GetImportDecisions(_audioFiles, _artist);
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.IsAny<ParsedTrackInfo>()), Times.Exactly(_audioFiles.Count));
|
||||
Mocker.GetMock<IAugmentingService>()
|
||||
.Verify(c => c.Augment(It.IsAny<LocalTrack>(), It.IsAny<bool>()), Times.Exactly(_audioFiles.Count));
|
||||
|
||||
ExceptionVerification.ExpectedErrors(3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_file_quality_if_folder_quality_is_null()
|
||||
{
|
||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||
var expectedQuality = QualityParser.ParseQuality(_audioFiles.Single(), null, 0);
|
||||
|
||||
var result = Subject.GetImportDecisions(_audioFiles, _artist);
|
||||
|
||||
result.Single().LocalTrack.Quality.Should().Be(expectedQuality);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_file_language_if_folder_language_is_null()
|
||||
{
|
||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||
var expectedLanguage = LanguageParser.ParseLanguage(_audioFiles.Single());
|
||||
|
||||
var result = Subject.GetImportDecisions(_audioFiles, _artist);
|
||||
|
||||
result.Single().LocalTrack.Language.Should().Be(expectedLanguage);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_file_quality_if_file_quality_was_determined_by_name()
|
||||
{
|
||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||
var expectedQuality = QualityParser.ParseQuality(_audioFiles.Single(), null, 0);
|
||||
|
||||
var result = Subject.GetImportDecisions(_audioFiles, _artist, new ParsedTrackInfo{Quality = new QualityModel(Quality.MP3_256) });
|
||||
|
||||
result.Single().LocalTrack.Quality.Should().Be(expectedQuality);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_folder_quality_when_file_quality_was_determined_by_the_extension()
|
||||
{
|
||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||
GivenVideoFiles(new string[] { @"C:\Test\Unsorted\The.Office.S03E115.mkv".AsOsAgnostic() });
|
||||
|
||||
_localTrack.Path = _audioFiles.Single();
|
||||
_localTrack.Quality.QualitySource = QualitySource.Extension;
|
||||
_localTrack.Quality.Quality = Quality.MP3_256;
|
||||
|
||||
var expectedQuality = new QualityModel(Quality.MP3_256);
|
||||
|
||||
var result = Subject.GetImportDecisions(_audioFiles, _artist, new ParsedTrackInfo { Quality = expectedQuality });
|
||||
|
||||
result.Single().LocalTrack.Quality.Should().Be(expectedQuality);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_folder_quality_when_greater_than_file_quality()
|
||||
{
|
||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||
GivenVideoFiles(new string[] { @"C:\Test\Unsorted\The.Office.S03E115.mkv".AsOsAgnostic() });
|
||||
|
||||
_localTrack.Path = _audioFiles.Single();
|
||||
_localTrack.Quality.Quality = Quality.MP3_256;
|
||||
|
||||
var expectedQuality = new QualityModel(Quality.MP3_256);
|
||||
|
||||
var result = Subject.GetImportDecisions(_audioFiles, _artist, new ParsedTrackInfo { Quality = expectedQuality });
|
||||
|
||||
result.Single().LocalTrack.Quality.Should().Be(expectedQuality);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_folder_language_when_greater_than_file_language()
|
||||
{
|
||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||
GivenVideoFiles(new string[] { @"C:\Test\Unsorted\The.Office.S03E115.Spanish.mkv".AsOsAgnostic() });
|
||||
|
||||
_localTrack.Path = _audioFiles.Single();
|
||||
_localTrack.Quality.Quality = Quality.MP3_320;
|
||||
_localTrack.Language = Language.Spanish;
|
||||
|
||||
var expectedLanguage = Language.French;
|
||||
|
||||
var result = Subject.GetImportDecisions(_audioFiles, _artist, new ParsedTrackInfo { Language = expectedLanguage, Quality = new QualityModel(Quality.MP3_192) });
|
||||
|
||||
result.Single().LocalTrack.Language.Should().Be(expectedLanguage);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_throw_if_episodes_are_not_found()
|
||||
public void should_not_throw_if_release_not_identified()
|
||||
{
|
||||
GivenSpecifications(_pass1);
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.IsAny<ParsedTrackInfo>()))
|
||||
.Returns(new LocalTrack() { Path = "test" });
|
||||
_audioFiles = new List<string>
|
||||
{
|
||||
"The.Office.S03E115.DVDRip.XviD-OSiTV",
|
||||
"The.Office.S03E115.DVDRip.XviD-OSiTV",
|
||||
"The.Office.S03E115.DVDRip.XviD-OSiTV"
|
||||
};
|
||||
|
||||
GivenVideoFiles(_audioFiles);
|
||||
|
||||
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>()))
|
||||
.Returns((List<LocalTrack> tracks, Artist artist, Album album, AlbumRelease release, bool newDownload, bool singleRelease) => {
|
||||
return new List<LocalAlbumRelease> { new LocalAlbumRelease(tracks) };
|
||||
});
|
||||
|
||||
var decisions = Subject.GetImportDecisions(_audioFiles, _artist);
|
||||
|
||||
Mocker.GetMock<IAugmentingService>()
|
||||
.Verify(c => c.Augment(It.IsAny<LocalTrack>(), It.IsAny<bool>()), Times.Exactly(_audioFiles.Count));
|
||||
|
||||
decisions.Should().HaveCount(3);
|
||||
decisions.First().Rejections.Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_throw_if_tracks_are_not_found()
|
||||
{
|
||||
GivenSpecifications(_pass1);
|
||||
|
||||
_audioFiles = new List<string>
|
||||
{
|
||||
@@ -274,127 +327,18 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
||||
|
||||
var decisions = Subject.GetImportDecisions(_audioFiles, _artist);
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.IsAny<ParsedTrackInfo>()), Times.Exactly(_audioFiles.Count));
|
||||
Mocker.GetMock<IAugmentingService>()
|
||||
.Verify(c => c.Augment(It.IsAny<LocalTrack>(), It.IsAny<bool>()), Times.Exactly(_audioFiles.Count));
|
||||
|
||||
decisions.Should().HaveCount(3);
|
||||
decisions.First().Rejections.Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_use_folder_for_full_season()
|
||||
{
|
||||
var videoFiles = new[]
|
||||
{
|
||||
@"C:\Test\Unsorted\Artist.Title.S01\S01E01.mkv".AsOsAgnostic(),
|
||||
@"C:\Test\Unsorted\Artist.Title.S01\S01E02.mkv".AsOsAgnostic(),
|
||||
@"C:\Test\Unsorted\Artist.Title.S01\S01E03.mkv".AsOsAgnostic()
|
||||
};
|
||||
|
||||
GivenSpecifications(_pass1);
|
||||
GivenVideoFiles(videoFiles);
|
||||
|
||||
var folderInfo = Parser.Parser.ParseMusicTitle("Artist.Title.S01");
|
||||
|
||||
Subject.GetImportDecisions(_audioFiles, _artist, folderInfo);
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), null), Times.Exactly(3));
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.Is<ParsedTrackInfo>(p => p != null)), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_use_folder_when_it_contains_more_than_one_valid_video_file()
|
||||
{
|
||||
var videoFiles = new[]
|
||||
{
|
||||
@"C:\Test\Unsorted\Artist.Title.S01E01\S01E01.mkv".AsOsAgnostic(),
|
||||
@"C:\Test\Unsorted\Artist.Title.S01E01\1x01.mkv".AsOsAgnostic()
|
||||
};
|
||||
|
||||
GivenSpecifications(_pass1);
|
||||
GivenVideoFiles(videoFiles);
|
||||
|
||||
var folderInfo = Parser.Parser.ParseMusicTitle("Artist.Title.S01E01");
|
||||
|
||||
Subject.GetImportDecisions(_audioFiles, _artist, folderInfo);
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), null), Times.Exactly(2));
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.Is<ParsedTrackInfo>(p => p != null)), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_folder_when_only_one_video_file()
|
||||
{
|
||||
var videoFiles = new[]
|
||||
{
|
||||
@"C:\Test\Unsorted\Artist.Title.S01E01\S01E01.mkv".AsOsAgnostic()
|
||||
};
|
||||
|
||||
GivenSpecifications(_pass1);
|
||||
GivenVideoFiles(videoFiles);
|
||||
|
||||
var folderInfo = Parser.Parser.ParseMusicTitle("Artist.Title.S01E01");
|
||||
|
||||
Subject.GetImportDecisions(_audioFiles, _artist, folderInfo);
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.IsAny<ParsedTrackInfo>()), Times.Exactly(1));
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), null), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_use_folder_name_if_file_name_is_scene_name()
|
||||
{
|
||||
var videoFiles = new[]
|
||||
{
|
||||
@"C:\Test\Unsorted\Artist.Title.S01E01.720p.HDTV-LOL\Artist.Title.S01E01.720p.HDTV-LOL.mkv".AsOsAgnostic()
|
||||
};
|
||||
|
||||
GivenSpecifications(_pass1);
|
||||
GivenVideoFiles(videoFiles);
|
||||
|
||||
var folderInfo = Parser.Parser.ParseMusicTitle("Artist.Title.S01E01.720p.HDTV-LOL");
|
||||
|
||||
Subject.GetImportDecisions(_audioFiles, _artist, folderInfo);
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), null), Times.Exactly(1));
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.Is<ParsedTrackInfo>(p => p != null)), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_use_folder_quality_when_it_is_unknown()
|
||||
{
|
||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||
|
||||
_artist.Profile = new Profile
|
||||
{
|
||||
Items = Qualities.QualityFixture.GetDefaultQualities(Quality.MP3_256, Quality.Unknown)
|
||||
};
|
||||
|
||||
|
||||
var folderQuality = new QualityModel(Quality.Unknown);
|
||||
|
||||
var result = Subject.GetImportDecisions(_audioFiles, _artist, new ParsedTrackInfo { Quality = folderQuality});
|
||||
|
||||
result.Single().LocalTrack.Quality.Should().Be(_quality);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_a_decision_when_exception_is_caught()
|
||||
{
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.IsAny<ParsedTrackInfo>()))
|
||||
Mocker.GetMock<IAugmentingService>()
|
||||
.Setup(c => c.Augment(It.IsAny<LocalTrack>(), It.IsAny<bool>()))
|
||||
.Throws<TestException>();
|
||||
|
||||
_audioFiles = new List<string>
|
||||
|
||||
Reference in New Issue
Block a user