New: Use MediaInfo on File Parsing

pull/3732/head
Qstick 6 years ago
parent ada9b944dc
commit bfc467dd96

@ -125,7 +125,7 @@ namespace NzbDrone.Api.Movies
var files = _diskScanService.GetVideoFiles(f.Path); var files = _diskScanService.GetVideoFiles(f.Path);
var decisions = _importDecisionMaker.GetImportDecisions(files.ToList(), m, true); var decisions = _importDecisionMaker.GetImportDecisions(files.ToList(), m);
var decision = decisions.Where(d => d.Approved && !d.Rejections.Any()).FirstOrDefault(); var decision = decisions.Where(d => d.Approved && !d.Rejections.Any()).FirstOrDefault();
@ -136,10 +136,10 @@ namespace NzbDrone.Api.Movies
m.MovieFile = new MovieFile m.MovieFile = new MovieFile
{ {
Path = local.Path, Path = local.Path,
Edition = local.ParsedMovieInfo.Edition, Edition = local.Edition,
Quality = local.Quality, Quality = local.Quality,
MediaInfo = local.MediaInfo, MediaInfo = local.MediaInfo,
ReleaseGroup = local.ParsedMovieInfo.ReleaseGroup, ReleaseGroup = local.ReleaseGroup,
RelativePath = f.Path.GetRelativePath(local.Path) RelativePath = f.Path.GetRelativePath(local.Path)
}; };
} }

@ -95,7 +95,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, true), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once());
} }
[Test] [Test]
@ -113,7 +113,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, true), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once());
} }
[Test] [Test]
@ -135,7 +135,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 4), _movie, true), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 4), _movie), Times.Once());
} }
[Test] [Test]
@ -154,7 +154,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, true), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once());
} }
[Test] [Test]
@ -174,7 +174,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, true), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once());
} }
[Test] [Test]
@ -191,7 +191,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, true), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once());
} }
[Test] [Test]
@ -208,7 +208,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, true), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once());
} }
[Test] [Test]
@ -226,7 +226,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 2), _movie, true), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 2), _movie), Times.Once());
} }
[Test] [Test]
@ -243,7 +243,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 2), _movie, true), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 2), _movie), Times.Once());
} }
[Test] [Test]
@ -260,7 +260,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, true), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once());
} }
} }
} }

@ -172,11 +172,9 @@ namespace NzbDrone.Core.Test.MediaFiles
Mocker.GetMock<IDetectSample>() Mocker.GetMock<IDetectSample>()
.Setup(s => s.IsSample(It.IsAny<Movie>(), .Setup(s => s.IsSample(It.IsAny<Movie>(),
It.IsAny<QualityModel>(),
It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<long>(),
It.IsAny<bool>())) It.IsAny<bool>()))
.Returns(true); .Returns(DetectSampleResult.Sample);
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory)); Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
@ -244,11 +242,9 @@ namespace NzbDrone.Core.Test.MediaFiles
Mocker.GetMock<IDetectSample>() Mocker.GetMock<IDetectSample>()
.Setup(s => s.IsSample(It.IsAny<Movie>(), .Setup(s => s.IsSample(It.IsAny<Movie>(),
It.IsAny<QualityModel>(),
It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<long>(),
It.IsAny<bool>())) It.IsAny<bool>()))
.Returns(true); .Returns(DetectSampleResult.Sample);
Mocker.GetMock<IDiskProvider>() Mocker.GetMock<IDiskProvider>()
.Setup(s => s.GetFiles(It.IsAny<string>(), SearchOption.AllDirectories)) .Setup(s => s.GetFiles(It.IsAny<string>(), SearchOption.AllDirectories))
@ -289,7 +285,7 @@ namespace NzbDrone.Core.Test.MediaFiles
Subject.ProcessPath(fileName); Subject.ProcessPath(fileName);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Movie>(), It.IsAny<DownloadClientItem>(), It.IsAny<ParsedMovieInfo>(), true, false), Times.Once()); .Verify(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Movie>(), It.IsAny<DownloadClientItem>(), It.IsAny<ParsedMovieInfo>(), true), Times.Once());
} }
[Test] [Test]
@ -313,7 +309,7 @@ namespace NzbDrone.Core.Test.MediaFiles
var result = Subject.ProcessPath(fileName); var result = Subject.ProcessPath(fileName);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Movie>(), It.IsAny<DownloadClientItem>(), null, true, false), Times.Once()); .Verify(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Movie>(), It.IsAny<DownloadClientItem>(), null, true), Times.Once());
} }
[Test] [Test]
@ -355,11 +351,9 @@ namespace NzbDrone.Core.Test.MediaFiles
Mocker.GetMock<IDetectSample>() Mocker.GetMock<IDetectSample>()
.Setup(s => s.IsSample(It.IsAny<Movie>(), .Setup(s => s.IsSample(It.IsAny<Movie>(),
It.IsAny<QualityModel>(),
It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<long>(),
It.IsAny<bool>())) It.IsAny<bool>()))
.Returns(true); .Returns(DetectSampleResult.Sample);
Mocker.GetMock<IDiskProvider>() Mocker.GetMock<IDiskProvider>()
.Setup(s => s.GetFileSize(It.IsAny<string>())) .Setup(s => s.GetFileSize(It.IsAny<string>()))

@ -22,7 +22,7 @@ namespace NzbDrone.Core.Test.MediaFiles
{ {
[TestFixture] [TestFixture]
//TODO: Update all of this for movies. //TODO: Update all of this for movies.
public class ImportApprovedEpisodesFixture : CoreTest<ImportApprovedMovie> public class ImportApprovedMoviesFixture : CoreTest<ImportApprovedMovie>
{ {
private List<ImportDecision> _rejectedDecisions; private List<ImportDecision> _rejectedDecisions;
private List<ImportDecision> _approvedDecisions; private List<ImportDecision> _approvedDecisions;
@ -51,10 +51,7 @@ namespace NzbDrone.Core.Test.MediaFiles
Movie = movie, Movie = movie,
Path = Path.Combine(movie.Path, "30 Rock - S01E01 - Pilot.avi"), Path = Path.Combine(movie.Path, "30 Rock - S01E01 - Pilot.avi"),
Quality = new QualityModel(), Quality = new QualityModel(),
ParsedMovieInfo = new ParsedMovieInfo() ReleaseGroup = "DRONE"
{
ReleaseGroup = "DRONE"
}
})); }));

@ -15,29 +15,29 @@ using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Movies; using NzbDrone.Core.Movies;
using NzbDrone.Test.Common; using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MediaFiles.EpisodeFileMovingServiceTests namespace NzbDrone.Core.Test.MediaFiles.MovieFileMovingServiceTests
{ {
[TestFixture] [TestFixture]
public class MoveEpisodeFileFixture : CoreTest<MovieFileMovingService> public class MoveMovieFileFixture : CoreTest<MovieFileMovingService>
{ {
private Movie _series; private Movie _movie;
private MovieFile _episodeFile; private MovieFile _movieFile;
private LocalMovie _localEpisode; private LocalMovie _localMovie;
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
_series = Builder<Movie>.CreateNew() _movie = Builder<Movie>.CreateNew()
.With(s => s.Path = @"C:\Test\TV\Series".AsOsAgnostic()) .With(s => s.Path = @"C:\Test\Movies\Movie".AsOsAgnostic())
.Build(); .Build();
_episodeFile = Builder<MovieFile>.CreateNew() _movieFile = Builder<MovieFile>.CreateNew()
.With(f => f.Path = null) .With(f => f.Path = null)
.With(f => f.RelativePath = @"Season 1\File.avi") .With(f => f.RelativePath = @"File.avi")
.Build(); .Build();
_localEpisode = Builder<LocalMovie>.CreateNew() _localMovie = Builder<LocalMovie>.CreateNew()
.With(l => l.Movie = _series) .With(l => l.Movie = _movie)
.Build(); .Build();
Mocker.GetMock<IBuildFileNames>() Mocker.GetMock<IBuildFileNames>()
@ -46,9 +46,9 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeFileMovingServiceTests
Mocker.GetMock<IBuildFileNames>() Mocker.GetMock<IBuildFileNames>()
.Setup(s => s.BuildFilePath(It.IsAny<Movie>(), It.IsAny<string>(), It.IsAny<string>())) .Setup(s => s.BuildFilePath(It.IsAny<Movie>(), It.IsAny<string>(), It.IsAny<string>()))
.Returns(@"C:\Test\TV\Series\File Name.avi".AsOsAgnostic()); .Returns(@"C:\Test\Movies\Movie\File Name.avi".AsOsAgnostic());
var rootFolder = @"C:\Test\TV\".AsOsAgnostic(); var rootFolder = @"C:\Test\Movies\".AsOsAgnostic();
Mocker.GetMock<IDiskProvider>() Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FolderExists(rootFolder)) .Setup(s => s.FolderExists(rootFolder))
.Returns(true); .Returns(true);
@ -67,7 +67,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeFileMovingServiceTests
.Setup(s => s.InheritFolderPermissions(It.IsAny<string>())) .Setup(s => s.InheritFolderPermissions(It.IsAny<string>()))
.Throws<UnauthorizedAccessException>(); .Throws<UnauthorizedAccessException>();
Subject.MoveMovieFile(_episodeFile, _localEpisode); Subject.MoveMovieFile(_movieFile, _localMovie);
} }
[Test] [Test]
@ -79,13 +79,13 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeFileMovingServiceTests
.Setup(s => s.InheritFolderPermissions(It.IsAny<string>())) .Setup(s => s.InheritFolderPermissions(It.IsAny<string>()))
.Throws<InvalidOperationException>(); .Throws<InvalidOperationException>();
Subject.MoveMovieFile(_episodeFile, _localEpisode); Subject.MoveMovieFile(_movieFile, _localMovie);
} }
[Test] [Test]
public void should_notify_on_series_folder_creation() public void should_notify_on_movie_folder_creation()
{ {
Subject.MoveMovieFile(_episodeFile, _localEpisode); Subject.MoveMovieFile(_movieFile, _localMovie);
Mocker.GetMock<IEventAggregator>() Mocker.GetMock<IEventAggregator>()
.Verify(s => s.PublishEvent<MovieFolderCreatedEvent>(It.Is<MovieFolderCreatedEvent>(p => .Verify(s => s.PublishEvent<MovieFolderCreatedEvent>(It.Is<MovieFolderCreatedEvent>(p =>
@ -93,13 +93,13 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeFileMovingServiceTests
} }
[Test] [Test]
public void should_not_notify_if_series_folder_already_exists() public void should_not_notify_if_movie_folder_already_exists()
{ {
Mocker.GetMock<IDiskProvider>() Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FolderExists(_series.Path)) .Setup(s => s.FolderExists(_movie.Path))
.Returns(true); .Returns(true);
Subject.MoveMovieFile(_episodeFile, _localEpisode); Subject.MoveMovieFile(_movieFile, _localMovie);
Mocker.GetMock<IEventAggregator>() Mocker.GetMock<IEventAggregator>()
.Verify(s => s.PublishEvent<MovieFolderCreatedEvent>(It.Is<MovieFolderCreatedEvent>(p => .Verify(s => s.PublishEvent<MovieFolderCreatedEvent>(It.Is<MovieFolderCreatedEvent>(p =>

@ -0,0 +1,73 @@
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Languages;
using NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using System.Collections.Generic;
namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
{
[TestFixture]
public class AggregateLanguageFixture : CoreTest<AggregateLanguage>
{
private LocalMovie _localMovie;
[SetUp]
public void Setup()
{
_localMovie = Builder<LocalMovie>.CreateNew()
.With(l => l.DownloadClientMovieInfo = null)
.With(l => l.FolderMovieInfo = null)
.With(l => l.FileMovieInfo = null)
.Build();
}
private ParsedMovieInfo GetParsedMovieInfo(Language language)
{
return new ParsedMovieInfo
{
Languages = new List<Language> { language }
};
}
[Test]
public void should_return_file_language_when_only_file_info_is_known()
{
_localMovie.FileMovieInfo = GetParsedMovieInfo(Language.English);
Subject.Aggregate(_localMovie, false).Languages.Should().Contain(_localMovie.FileMovieInfo.Languages);
}
[Test]
public void should_return_folder_language_when_folder_info_is_known()
{
_localMovie.FolderMovieInfo = GetParsedMovieInfo(Language.English);
_localMovie.FileMovieInfo = GetParsedMovieInfo(Language.English);
Subject.Aggregate(_localMovie, false).Languages.Should().Contain(_localMovie.FolderMovieInfo.Languages);
}
[Test]
public void should_return_download_client_item_language_when_download_client_item_info_is_known()
{
_localMovie.DownloadClientMovieInfo = GetParsedMovieInfo(Language.English);
_localMovie.FolderMovieInfo = GetParsedMovieInfo(Language.English);
_localMovie.FileMovieInfo = GetParsedMovieInfo(Language.English);
Subject.Aggregate(_localMovie, false).Languages.Should().Contain(_localMovie.DownloadClientMovieInfo.Languages);
}
[Test]
public void should_return_file_language_when_file_language_is_higher_than_others()
{
_localMovie.DownloadClientMovieInfo = GetParsedMovieInfo(Language.English);
_localMovie.FolderMovieInfo = GetParsedMovieInfo(Language.English);
_localMovie.FileMovieInfo = GetParsedMovieInfo(Language.French);
Subject.Aggregate(_localMovie, false).Languages.Should().Contain(_localMovie.FileMovieInfo.Languages);
}
}
}

@ -0,0 +1,94 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators;
using NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenters.Quality;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
{
[TestFixture]
public class AugmentQualityFixture : CoreTest<AggregateQuality>
{
private Mock<IAugmentQuality> _mediaInfoAugmenter;
private Mock<IAugmentQuality> _fileExtensionAugmenter;
private Mock<IAugmentQuality> _nameAugmenter;
private IEnumerable<IAugmentQuality> _qualityAugmenters;
[SetUp]
public void Setup()
{
_mediaInfoAugmenter = new Mock<IAugmentQuality>();
_fileExtensionAugmenter = new Mock<IAugmentQuality>();
_nameAugmenter = new Mock<IAugmentQuality>();
_mediaInfoAugmenter.Setup(s => s.AugmentQuality(It.IsAny<LocalMovie>()))
.Returns(AugmentQualityResult.ResolutionOnly(Resolution.R1080P, Confidence.MediaInfo));
_fileExtensionAugmenter.Setup(s => s.AugmentQuality(It.IsAny<LocalMovie>()))
.Returns(new AugmentQualityResult(Source.TV, Confidence.Fallback, Resolution.R720P, Confidence.Fallback, new Revision()));
_nameAugmenter.Setup(s => s.AugmentQuality(It.IsAny<LocalMovie>()))
.Returns(new AugmentQualityResult(Source.TV, Confidence.Default, Resolution.R480P, Confidence.Default, new Revision()));
}
private void GivenAugmenters(params Mock<IAugmentQuality>[] mocks)
{
Mocker.SetConstant<IEnumerable<IAugmentQuality>>(mocks.Select(c => c.Object));
}
[Test]
public void should_return_HDTV720_from_extension_when_other_augments_are_null()
{
var nullMock = new Mock<IAugmentQuality>();
nullMock.Setup(s => s.AugmentQuality(It.IsAny<LocalMovie>()))
.Returns<LocalMovie>(l => null);
GivenAugmenters(_fileExtensionAugmenter, nullMock);
var result = Subject.Aggregate(new LocalMovie(), false);
result.Quality.QualityDetectionSource.Should().Be(QualityDetectionSource.Extension);
result.Quality.Quality.Should().Be(Quality.HDTV720p);
}
[Test]
public void should_return_SDTV_when_HDTV720_came_from_extension()
{
GivenAugmenters(_fileExtensionAugmenter, _nameAugmenter);
var result = Subject.Aggregate(new LocalMovie(), false);
result.Quality.QualityDetectionSource.Should().Be(QualityDetectionSource.Name);
result.Quality.Quality.Should().Be(Quality.SDTV);
}
[Test]
public void should_return_HDTV1080p_when_HDTV720_came_from_extension_and_mediainfo_indicates_1080()
{
GivenAugmenters(_fileExtensionAugmenter, _mediaInfoAugmenter);
var result = Subject.Aggregate(new LocalMovie(), false);
result.Quality.QualityDetectionSource.Should().Be(QualityDetectionSource.MediaInfo);
result.Quality.Quality.Should().Be(Quality.HDTV1080p);
}
[Test]
public void should_return_HDTV1080p_when_SDTV_came_from_name_and_mediainfo_indicates_1080()
{
GivenAugmenters(_nameAugmenter, _mediaInfoAugmenter);
var result = Subject.Aggregate(new LocalMovie(), false);
result.Quality.QualityDetectionSource.Should().Be(QualityDetectionSource.MediaInfo);
result.Quality.Quality.Should().Be(Quality.HDTV1080p);
}
}
}

@ -0,0 +1,69 @@
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenters.Quality;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.CustomFormats;
namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenters.Quality
{
[TestFixture]
public class AugmentQualityFromMediaInfoFixture : CoreTest<AugmentQualityFromMediaInfo>
{
[Test]
public void should_return_null_if_media_info_is_null()
{
var localMovie = Builder<LocalMovie>.CreateNew()
.With(l => l.MediaInfo = null)
.Build();
Subject.AugmentQuality(localMovie).Should().Be(null);
}
[Test]
public void should_return_null_if_media_info_width_is_zero()
{
var mediaInfo = Builder<MediaInfoModel>.CreateNew()
.With(m => m.Width = 0)
.Build();
var localMovie = Builder<LocalMovie>.CreateNew()
.With(l => l.MediaInfo = mediaInfo)
.Build();
Subject.AugmentQuality(localMovie).Should().Be(null);
}
[TestCase(4096, Resolution.R2160P)] // True 4K
[TestCase(4000, Resolution.R2160P)]
[TestCase(3840, Resolution.R2160P)] // 4K UHD
[TestCase(3200, Resolution.R2160P)]
[TestCase(2000, Resolution.R1080P)]
[TestCase(1920, Resolution.R1080P)] // Full HD
[TestCase(1800, Resolution.R1080P)]
[TestCase(1490, Resolution.R720P)]
[TestCase(1280, Resolution.R720P)] // HD
[TestCase(1200, Resolution.R720P)]
[TestCase(800, Resolution.R480P)]
[TestCase(720, Resolution.R480P)] // SDTV
[TestCase(600, Resolution.R480P)]
[TestCase(100, Resolution.R480P)]
public void should_return_closest_resolution(int mediaInfoWidth, Resolution expectedResolution)
{
var mediaInfo = Builder<MediaInfoModel>.CreateNew()
.With(m => m.Width = mediaInfoWidth)
.Build();
var localMovie = Builder<LocalMovie>.CreateNew()
.With(l => l.MediaInfo = mediaInfo)
.Build();
var result = Subject.AugmentQuality(localMovie);
result.Should().NotBe(null);
result.Resolution.Should().Be(expectedResolution);
}
}
}

@ -10,11 +10,12 @@ using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Movies; using NzbDrone.Core.Movies;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MediaFiles.MovieImport namespace NzbDrone.Core.Test.MediaFiles.MovieImport
{ {
[TestFixture] [TestFixture]
public class SampleServiceFixture : CoreTest<DetectSample> public class DetectSampleFixture : CoreTest<DetectSample>
{ {
private Movie _movie; private Movie _movie;
private LocalMovie _localMovie; private LocalMovie _localMovie;
@ -26,17 +27,13 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
.With(s => s.Runtime = 30) .With(s => s.Runtime = 30)
.Build(); .Build();
_localMovie = new LocalMovie
{
Path = @"C:\Test\30 Rock\30.rock.s01e01.avi",
Movie = _movie,
Quality = new QualityModel(Quality.HDTV720p)
};
}
private void GivenFileSize(long size) _localMovie = new LocalMovie
{ {
_localMovie.Size = size; Path = @"C:\Test\30 Rock\30.rock.s01e01.avi",
Movie = _movie,
Quality = new QualityModel(Quality.HDTV720p)
};
} }
private void GivenRuntime(int seconds) private void GivenRuntime(int seconds)
@ -51,7 +48,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
{ {
_localMovie.Path = @"C:\Test\some.show.s01e01.flv"; _localMovie.Path = @"C:\Test\some.show.s01e01.flv";
ShouldBeFalse(); ShouldBeNotSample();
Mocker.GetMock<IVideoFileInfoReader>().Verify(c => c.GetRunTime(It.IsAny<string>()), Times.Never()); Mocker.GetMock<IVideoFileInfoReader>().Verify(c => c.GetRunTime(It.IsAny<string>()), Times.Never());
} }
@ -61,7 +58,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
{ {
_localMovie.Path = @"C:\Test\some.show.s01e01.strm"; _localMovie.Path = @"C:\Test\some.show.s01e01.strm";
ShouldBeFalse(); ShouldBeNotSample();
Mocker.GetMock<IVideoFileInfoReader>().Verify(c => c.GetRunTime(It.IsAny<string>()), Times.Never()); Mocker.GetMock<IVideoFileInfoReader>().Verify(c => c.GetRunTime(It.IsAny<string>()), Times.Never());
} }
@ -70,12 +67,9 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
public void should_use_runtime() public void should_use_runtime()
{ {
GivenRuntime(120); GivenRuntime(120);
GivenFileSize(1000.Megabytes());
Subject.IsSample(_localMovie.Movie, Subject.IsSample(_localMovie.Movie,
_localMovie.Quality,
_localMovie.Path, _localMovie.Path,
_localMovie.Size,
false); false);
Mocker.GetMock<IVideoFileInfoReader>().Verify(v => v.GetRunTime(It.IsAny<string>()), Times.Once()); Mocker.GetMock<IVideoFileInfoReader>().Verify(v => v.GetRunTime(It.IsAny<string>()), Times.Once());
@ -86,7 +80,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
{ {
GivenRuntime(60); GivenRuntime(60);
ShouldBeTrue(); ShouldBeSample();
} }
[Test] [Test]
@ -94,7 +88,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
{ {
GivenRuntime(600); GivenRuntime(600);
ShouldBeFalse(); ShouldBeNotSample();
} }
[Test] [Test]
@ -103,51 +97,53 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
_movie.Runtime = 6; _movie.Runtime = 6;
GivenRuntime(299); GivenRuntime(299);
ShouldBeFalse(); ShouldBeNotSample();
} }
[Test] [Test]
public void should_fall_back_to_file_size_if_mediainfo_dll_not_found_acceptable_size() public void should_return_false_if_runtime_greater_than_anime_short_minimum()
{ {
Mocker.GetMock<IVideoFileInfoReader>() _movie.Runtime = 2;
.Setup(s => s.GetRunTime(It.IsAny<string>())) GivenRuntime(60);
.Throws<DllNotFoundException>();
GivenFileSize(1000.Megabytes()); ShouldBeNotSample();
ShouldBeFalse();
} }
[Test] [Test]
public void should_fall_back_to_file_size_if_mediainfo_dll_not_found_undersize() public void should_return_true_if_runtime_less_than_anime_short_minimum()
{ {
Mocker.GetMock<IVideoFileInfoReader>() _movie.Runtime = 2;
.Setup(s => s.GetRunTime(It.IsAny<string>())) GivenRuntime(10);
.Throws<DllNotFoundException>();
GivenFileSize(1.Megabytes()); ShouldBeSample();
ShouldBeTrue();
} }
[Test]
public void should_return_indeterminate_if_mediainfo_result_is_null()
{
Mocker.GetMock<IVideoFileInfoReader>()
.Setup(s => s.GetRunTime(It.IsAny<string>()))
.Returns((TimeSpan?)null);
Subject.IsSample(_localMovie.Movie,
_localMovie.Path,
false).Should().Be(DetectSampleResult.Indeterminate);
ExceptionVerification.ExpectedErrors(1);
}
private void ShouldBeSample()
private void ShouldBeTrue()
{ {
Subject.IsSample(_localMovie.Movie, Subject.IsSample(_localMovie.Movie,
_localMovie.Quality, _localMovie.Path,
_localMovie.Path, false).Should().Be(DetectSampleResult.Sample);
_localMovie.Size,
false).Should().BeTrue();
} }
private void ShouldBeFalse() private void ShouldBeNotSample()
{ {
Subject.IsSample(_localMovie.Movie, Subject.IsSample(_localMovie.Movie,
_localMovie.Quality,
_localMovie.Path, _localMovie.Path,
_localMovie.Size, false).Should().Be(DetectSampleResult.NotSample);
false).Should().BeFalse();
} }
} }
} }

@ -15,6 +15,7 @@ using NzbDrone.Core.Movies;
using NzbDrone.Test.Common; using NzbDrone.Test.Common;
using FizzWare.NBuilder; using FizzWare.NBuilder;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles.MovieImport.Aggregation;
namespace NzbDrone.Core.Test.MediaFiles.MovieImport namespace NzbDrone.Core.Test.MediaFiles.MovieImport
{ {
@ -75,10 +76,6 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
Quality = _quality Quality = _quality
}; };
Mocker.GetMock<IParsingService>()
.Setup(c => c.GetLocalMovie(It.IsAny<string>(), It.IsAny<ParsedMovieInfo>(), It.IsAny<Movie>(), It.IsAny<List<object>>(), It.IsAny<bool>()))
.Returns(_localMovie);
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Setup(c => c.ParseMinimalPathMovieInfo(It.IsAny<string>())) .Setup(c => c.ParseMinimalPathMovieInfo(It.IsAny<string>()))
.Returns(_fileInfo); .Returns(_fileInfo);
@ -100,6 +97,16 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
.Returns(_videoFiles); .Returns(_videoFiles);
} }
private void GivenAugmentationSuccess()
{
Mocker.GetMock<IAggregationService>()
.Setup(s => s.Augment(It.IsAny<LocalMovie>(), It.IsAny<bool>()))
.Callback<LocalMovie, bool>((localMovie, otherFiles) =>
{
localMovie.Movie = _localMovie.Movie;
});
}
[Test] [Test]
public void should_call_all_specifications() public void should_call_all_specifications()
{ {
@ -108,12 +115,12 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
Subject.GetImportDecisions(_videoFiles, new Movie(), downloadClientItem, null, false); Subject.GetImportDecisions(_videoFiles, new Movie(), downloadClientItem, null, false);
_fail1.Verify(c => c.IsSatisfiedBy(_localMovie, downloadClientItem), Times.Once()); _fail1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), downloadClientItem), Times.Once());
_fail2.Verify(c => c.IsSatisfiedBy(_localMovie, downloadClientItem), Times.Once()); _fail2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), downloadClientItem), Times.Once());
_fail3.Verify(c => c.IsSatisfiedBy(_localMovie, downloadClientItem), Times.Once()); _fail3.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), downloadClientItem), Times.Once());
_pass1.Verify(c => c.IsSatisfiedBy(_localMovie, downloadClientItem), Times.Once()); _pass1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), downloadClientItem), Times.Once());
_pass2.Verify(c => c.IsSatisfiedBy(_localMovie, downloadClientItem), Times.Once()); _pass2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), downloadClientItem), Times.Once());
_pass3.Verify(c => c.IsSatisfiedBy(_localMovie, downloadClientItem), Times.Once()); _pass3.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), downloadClientItem), Times.Once());
} }
[Test] [Test]
@ -160,8 +167,8 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
{ {
GivenSpecifications(_pass1); GivenSpecifications(_pass1);
Mocker.GetMock<IParsingService>() Mocker.GetMock<IAggregationService>()
.Setup(c => c.GetLocalMovie(It.IsAny<string>(), It.IsAny<ParsedMovieInfo>(), It.IsAny<Movie>(), It.IsAny<List<object>>(), It.IsAny<bool>())) .Setup(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<bool>()))
.Throws<TestException>(); .Throws<TestException>();
_videoFiles = new List<string> _videoFiles = new List<string>
@ -175,8 +182,8 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
Subject.GetImportDecisions(_videoFiles, _movie); Subject.GetImportDecisions(_videoFiles, _movie);
Mocker.GetMock<IParsingService>() Mocker.GetMock<IAggregationService>()
.Verify(c => c.GetLocalMovie(It.IsAny<string>(), It.IsAny<ParsedMovieInfo>(), It.IsAny<Movie>(), It.IsAny<List<object>>(), It.IsAny<bool>()), Times.Exactly(_videoFiles.Count)); .Verify(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<bool>()), Times.Exactly(_videoFiles.Count));
ExceptionVerification.ExpectedErrors(3); ExceptionVerification.ExpectedErrors(3);
} }
@ -196,88 +203,16 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
var fileNames = _videoFiles.Select(System.IO.Path.GetFileName); var fileNames = _videoFiles.Select(System.IO.Path.GetFileName);
Mocker.GetMock<IParsingService>() Mocker.GetMock<IAggregationService>()
.Verify( .Setup(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<bool>()))
c => c.GetLocalMovie(It.IsAny<string>(), .Throws<TestException>();
It.Is<ParsedMovieInfo>(p => fileNames.Contains(p.SimpleReleaseTitle)), It.IsAny<Movie>(),
It.IsAny<List<object>>(), It.IsAny<bool>()), Times.Exactly(_videoFiles.Count));
}
[Test]
public void should_use_file_quality_if_folder_quality_is_null()
{
GivenSpecifications(_pass1, _pass2, _pass3);
var result = Subject.GetImportDecisions(_videoFiles, _movie);
result.Single().LocalMovie.Quality.Should().Be(_fileInfo.Quality);
}
[Test]
public void should_use_file_quality_if_file_quality_was_determined_by_name()
{
GivenSpecifications(_pass1, _pass2, _pass3);
var result = Subject.GetImportDecisions(_videoFiles, _movie, null, new ParsedMovieInfo{Quality = new QualityModel(Quality.SDTV)}, true);
result.Single().LocalMovie.Quality.Should().Be(_fileInfo.Quality);
}
[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() });
_localMovie.Path = _videoFiles.Single();
_localMovie.Quality.QualitySource = QualitySource.Extension;
_localMovie.Quality.Quality = Quality.HDTV720p;
var expectedQuality = new QualityModel(Quality.SDTV);
var result = Subject.GetImportDecisions(_videoFiles, _movie, null, new ParsedMovieInfo { Quality = expectedQuality }, true);
result.Single().LocalMovie.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() });
_localMovie.Path = _videoFiles.Single();
_localMovie.Quality.Quality = Quality.HDTV720p;
var expectedQuality = new QualityModel(Quality.Bluray720p);
var result = Subject.GetImportDecisions(_videoFiles, _movie, null, new ParsedMovieInfo { Quality = expectedQuality }, true);
result.Single().LocalMovie.Quality.Should().Be(expectedQuality);
}
[Test]
public void should_not_use_folder_quality_when_it_is_unknown()
{
GivenSpecifications(_pass1, _pass2, _pass3);
_movie.Profile = new Profile
{
Items = Qualities.QualityFixture.GetDefaultQualities(Quality.DVD, Quality.Unknown)
};
var folderQuality = new QualityModel(Quality.Unknown);
var result = Subject.GetImportDecisions(_videoFiles, _movie, null, new ParsedMovieInfo { Quality = folderQuality}, true);
result.Single().LocalMovie.Quality.Should().Be(_quality);
} }
[Test] [Test]
public void should_return_a_decision_when_exception_is_caught() public void should_return_a_decision_when_exception_is_caught()
{ {
Mocker.GetMock<IParsingService>() Mocker.GetMock<IAggregationService>()
.Setup(c => c.GetLocalMovie(It.IsAny<string>(), It.IsAny<ParsedMovieInfo>(), It.IsAny<Movie>(), It.IsAny<List<object>>(), It.IsAny<bool>())) .Setup(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<bool>()))
.Throws<TestException>(); .Throws<TestException>();
_videoFiles = new List<string> _videoFiles = new List<string>

@ -18,7 +18,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Specifications
{ {
_localMovie = Builder<LocalMovie>.CreateNew() _localMovie = Builder<LocalMovie>.CreateNew()
.With(l => l.Path = @"C:\Test\Unsorted\Series.Title.S01E01.720p.HDTV-Sonarr\S01E05.mkv".AsOsAgnostic()) .With(l => l.Path = @"C:\Test\Unsorted\Series.Title.S01E01.720p.HDTV-Sonarr\S01E05.mkv".AsOsAgnostic())
.With(l => l.ParsedMovieInfo = .With(l => l.FileMovieInfo =
Builder<ParsedMovieInfo>.CreateNew() Builder<ParsedMovieInfo>.CreateNew()
.Build()) .Build())
.Build(); .Build();

@ -291,10 +291,13 @@
<Compile Include="MediaFiles\DiskScanServiceTests\ScanFixture.cs" /> <Compile Include="MediaFiles\DiskScanServiceTests\ScanFixture.cs" />
<Compile Include="MediaFiles\DownloadedMoviesCommandServiceFixture.cs" /> <Compile Include="MediaFiles\DownloadedMoviesCommandServiceFixture.cs" />
<Compile Include="MediaFiles\DownloadedMoviesImportServiceFixture.cs" /> <Compile Include="MediaFiles\DownloadedMoviesImportServiceFixture.cs" />
<Compile Include="MediaFiles\EpisodeFileMovingServiceTests\MoveEpisodeFileFixture.cs" />
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatterTests\FormatVideoDynamicRangeFixture.cs" /> <Compile Include="MediaFiles\MediaInfo\MediaInfoFormatterTests\FormatVideoDynamicRangeFixture.cs" />
<Compile Include="MediaFiles\MovieFileMovingServiceTests\MoveMovieFileFixture.cs" />
<Compile Include="MediaFiles\MovieImport\Aggregation\Aggregators\AggregateLanguageFixture.cs" />
<Compile Include="MediaFiles\MovieImport\Aggregation\Aggregators\AggregateQualityFixture.cs" />
<Compile Include="MediaFiles\MovieImport\Aggregation\Aggregators\Augmenters\Quality\AugmentQualityFromMediaInfoFixture.cs" />
<Compile Include="MediaFiles\MovieImport\ImportDecisionMakerFixture.cs" /> <Compile Include="MediaFiles\MovieImport\ImportDecisionMakerFixture.cs" />
<Compile Include="MediaFiles\MovieImport\SampleServiceFixture.cs" /> <Compile Include="MediaFiles\MovieImport\DetectSampleFixture.cs" />
<Compile Include="MediaFiles\MovieImport\Specifications\FreeSpaceSpecificationFixture.cs" /> <Compile Include="MediaFiles\MovieImport\Specifications\FreeSpaceSpecificationFixture.cs" />
<Compile Include="MediaFiles\MovieImport\Specifications\GrabbedReleaseQualityFixture.cs" /> <Compile Include="MediaFiles\MovieImport\Specifications\GrabbedReleaseQualityFixture.cs" />
<Compile Include="MediaFiles\MovieImport\Specifications\MatchesFolderSpecificationFixture.cs" /> <Compile Include="MediaFiles\MovieImport\Specifications\MatchesFolderSpecificationFixture.cs" />
@ -302,7 +305,7 @@
<Compile Include="MediaFiles\MovieImport\Specifications\NotUnpackingSpecificationFixture.cs" /> <Compile Include="MediaFiles\MovieImport\Specifications\NotUnpackingSpecificationFixture.cs" />
<Compile Include="MediaFiles\MovieImport\Specifications\SameFileSpecificationFixture.cs" /> <Compile Include="MediaFiles\MovieImport\Specifications\SameFileSpecificationFixture.cs" />
<Compile Include="MediaFiles\MovieImport\Specifications\UpgradeSpecificationFixture.cs" /> <Compile Include="MediaFiles\MovieImport\Specifications\UpgradeSpecificationFixture.cs" />
<Compile Include="MediaFiles\ImportApprovedEpisodesFixture.cs" /> <Compile Include="MediaFiles\ImportApprovedMoviesFixture.cs" />
<Compile Include="MediaFiles\MediaFileRepositoryFixture.cs" /> <Compile Include="MediaFiles\MediaFileRepositoryFixture.cs" />
<Compile Include="MediaFiles\UpdateMovieFileQualityServiceFixture.cs" /> <Compile Include="MediaFiles\UpdateMovieFileQualityServiceFixture.cs" />
<Compile Include="Messaging\Commands\CommandQueueManagerFixture.cs" /> <Compile Include="Messaging\Commands\CommandQueueManagerFixture.cs" />

@ -43,7 +43,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests.AugmentersTests
var movieInfo = Subject.AugmentMovieInfo(MovieInfo, mediaInfo); var movieInfo = Subject.AugmentMovieInfo(MovieInfo, mediaInfo);
movieInfo.Quality.Resolution.ShouldBeEquivalentTo(realResolution); movieInfo.Quality.Resolution.ShouldBeEquivalentTo(realResolution);
movieInfo.Quality.QualitySource.ShouldBeEquivalentTo(QualitySource.MediaInfo); movieInfo.Quality.QualityDetectionSource.ShouldBeEquivalentTo(QualityDetectionSource.MediaInfo);
} }
[TestCase(Resolution.R720P, Source.BLURAY, Resolution.R1080P, Modifier.BRDISK)] [TestCase(Resolution.R720P, Source.BLURAY, Resolution.R1080P, Modifier.BRDISK)]
@ -83,7 +83,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests.AugmentersTests
var movieInfo = Subject.AugmentMovieInfo(MovieInfo, mediaInfo); var movieInfo = Subject.AugmentMovieInfo(MovieInfo, mediaInfo);
movieInfo.Quality.Resolution.ShouldBeEquivalentTo(resolution); movieInfo.Quality.Resolution.ShouldBeEquivalentTo(resolution);
movieInfo.Quality.QualitySource.ShouldBeEquivalentTo(QualitySource.Name); movieInfo.Quality.QualityDetectionSource.ShouldBeEquivalentTo(QualityDetectionSource.Name);
} }
} }
} }

@ -331,7 +331,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("White.Van.Man.2011.S02E01.WS.PDTV.x264-REPACK-TLA")] [TestCase("White.Van.Man.2011.S02E01.WS.PDTV.x264-REPACK-TLA")]
public void should_parse_quality_from_name(string title) public void should_parse_quality_from_name(string title)
{ {
QualityParser.ParseQuality(title).QualitySource.Should().Be(QualitySource.Name); QualityParser.ParseQuality(title).QualityDetectionSource.Should().Be(QualityDetectionSource.Name);
} }
[TestCase("Revolution.S01E02.Chained.Heat.mkv")] [TestCase("Revolution.S01E02.Chained.Heat.mkv")]
@ -341,7 +341,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("[CR] Sailor Moon - 004 [48CE2D0F].avi")] [TestCase("[CR] Sailor Moon - 004 [48CE2D0F].avi")]
public void should_parse_quality_from_extension(string title) public void should_parse_quality_from_extension(string title)
{ {
QualityParser.ParseQuality(title).QualitySource.Should().Be(QualitySource.Extension); QualityParser.ParseQuality(title).QualityDetectionSource.Should().Be(QualityDetectionSource.Extension);
} }
[TestCase("Movie.Title.2016.1080p.KORSUB.WEBRip.x264.AAC2.0-RADARR", "korsub")] [TestCase("Movie.Title.2016.1080p.KORSUB.WEBRip.x264.AAC2.0-RADARR", "korsub")]

@ -119,7 +119,7 @@ namespace NzbDrone.Core.MediaFiles
CleanMediaFiles(movie, mediaFileList); CleanMediaFiles(movie, mediaFileList);
var decisionsStopwatch = Stopwatch.StartNew(); var decisionsStopwatch = Stopwatch.StartNew();
var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, movie, true); var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, movie);
decisionsStopwatch.Stop(); decisionsStopwatch.Stop();
_logger.Trace("Import decisions complete for: {0} [{1}]", movie, decisionsStopwatch.Elapsed); _logger.Trace("Import decisions complete for: {0} [{1}]", movie, decisionsStopwatch.Elapsed);
_importApprovedMovies.Import(decisions, false); _importApprovedMovies.Import(decisions, false);

@ -130,7 +130,7 @@ namespace NzbDrone.Core.MediaFiles
var size = _diskProvider.GetFileSize(videoFile); var size = _diskProvider.GetFileSize(videoFile);
if (!_detectSample.IsSample(movie, QualityParser.ParseQuality(Path.GetFileName(videoFile)), videoFile, size, false)) if (_detectSample.IsSample(movie, videoFile, false) == DetectSampleResult.NotSample)
{ {
_logger.Warn("Non-sample file detected: [{0}]", videoFile); _logger.Warn("Non-sample file detected: [{0}]", videoFile);
return false; return false;
@ -198,7 +198,7 @@ namespace NzbDrone.Core.MediaFiles
} }
} }
var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), movie, downloadClientItem, folderInfo, true, false); var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), movie, downloadClientItem, folderInfo, true);
var importResults = _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode); var importResults = _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode);
if ((downloadClientItem == null || downloadClientItem.CanBeRemoved) && if ((downloadClientItem == null || downloadClientItem.CanBeRemoved) &&
@ -252,7 +252,7 @@ namespace NzbDrone.Core.MediaFiles
} }
} }
var decisions = _importDecisionMaker.GetImportDecisions(new List<string>() { fileInfo.FullName }, movie, downloadClientItem, null, true, false); var decisions = _importDecisionMaker.GetImportDecisions(new List<string>() { fileInfo.FullName }, movie, downloadClientItem, null, true);
return _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode); return _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode);
} }

@ -0,0 +1,24 @@
using System;
using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation
{
public class AugmentingFailedException : NzbDroneException
{
public AugmentingFailedException(string message, params object[] args) : base(message, args)
{
}
public AugmentingFailedException(string message) : base(message)
{
}
public AugmentingFailedException(string message, Exception innerException, params object[] args) : base(message, innerException, args)
{
}
public AugmentingFailedException(string message, Exception innerException) : base(message, innerException)
{
}
}
}

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.IO;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation
{
public interface IAggregationService
{
LocalMovie Augment(LocalMovie localMovie, bool otherFiles);
}
public class AggregationService : IAggregationService
{
private readonly IEnumerable<IAggregateLocalMovie> _augmenters;
private readonly IDiskProvider _diskProvider;
private readonly IVideoFileInfoReader _videoFileInfoReader;
private readonly IConfigService _configService;
private readonly Logger _logger;
public AggregationService(IEnumerable<IAggregateLocalMovie> augmenters,
IDiskProvider diskProvider,
IVideoFileInfoReader videoFileInfoReader,
IConfigService configService,
Logger logger)
{
_augmenters = augmenters;
_diskProvider = diskProvider;
_videoFileInfoReader = videoFileInfoReader;
_configService = configService;
_logger = logger;
}
public LocalMovie Augment(LocalMovie localMovie, bool otherFiles)
{
var isMediaFile = MediaFileExtensions.Extensions.Contains(Path.GetExtension(localMovie.Path));
if (localMovie.DownloadClientMovieInfo == null &&
localMovie.FolderMovieInfo == null &&
localMovie.FileMovieInfo == null)
{
if (isMediaFile)
{
throw new AugmentingFailedException("Unable to parse movie info from path: {0}", localMovie.Path);
}
}
localMovie.Size = _diskProvider.GetFileSize(localMovie.Path);
if (isMediaFile && (!localMovie.ExistingFile || _configService.EnableMediaInfo))
{
localMovie.MediaInfo = _videoFileInfoReader.GetMediaInfo(localMovie.Path);
}
foreach (var augmenter in _augmenters)
{
try
{
augmenter.Aggregate(localMovie, otherFiles);
}
catch (Exception ex)
{
_logger.Warn(ex, ex.Message);
}
}
return localMovie;
}
}
}

@ -0,0 +1,51 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators
{
public class AggregateLanguage : IAggregateLocalMovie
{
private readonly Logger _logger;
public AggregateLanguage(Logger logger)
{
_logger = logger;
}
public LocalMovie Aggregate(LocalMovie localMovie, bool otherFiles)
{
// Get languages in preferred order, download client item, folder and finally file.
// Non-English languages will be preferred later, in the event there is a conflict
// between parsed languages the more preferred item will be used.
var languages = new List<Language>();
languages.AddRange(GetLanguage(localMovie.DownloadClientMovieInfo));
languages.AddRange(GetLanguage(localMovie.FolderMovieInfo));
languages.AddRange(GetLanguage(localMovie.FileMovieInfo));
var language = new List<Language> { languages.FirstOrDefault(l => l != Language.English) ?? Language.English };
_logger.Debug("Using language: {0}", language.First());
localMovie.Languages = language;
return localMovie;
}
private List<Language> GetLanguage(ParsedMovieInfo parsedMovieInfo)
{
if (parsedMovieInfo == null)
{
// English is the default language when otherwise unknown
return new List<Language> { Language.English };
}
return parsedMovieInfo.Languages;
}
}
}

@ -0,0 +1,81 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenters.Quality;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators
{
public class AggregateQuality : IAggregateLocalMovie
{
private readonly IEnumerable<IAugmentQuality> _augmentQualities;
private readonly Logger _logger;
public AggregateQuality(IEnumerable<IAugmentQuality> augmentQualities,
Logger logger)
{
_augmentQualities = augmentQualities;
_logger = logger;
}
public LocalMovie Aggregate(LocalMovie localMovie, bool otherFiles)
{
var augmentedQualities = _augmentQualities.Select(a => a.AugmentQuality(localMovie))
.Where(a => a != null)
.OrderBy(a => a.SourceConfidence);
var source = Source.UNKNOWN;
var sourceConfidence = Confidence.Default;
var resolution = Resolution.Unknown;
var resolutionConfidence = Confidence.Default;
var revison = new Revision();
foreach (var augmentedQuality in augmentedQualities)
{
if (augmentedQuality.Source > source ||
augmentedQuality.SourceConfidence > sourceConfidence && augmentedQuality.Source != Source.UNKNOWN)
{
source = augmentedQuality.Source;
sourceConfidence = augmentedQuality.SourceConfidence;
}
if (augmentedQuality.Resolution > resolution ||
augmentedQuality.ResolutionConfidence > resolutionConfidence && augmentedQuality.Resolution > 0)
{
resolution = augmentedQuality.Resolution;
resolutionConfidence = augmentedQuality.ResolutionConfidence;
}
if (augmentedQuality.Revision != null && augmentedQuality.Revision > revison)
{
revison = augmentedQuality.Revision;
}
}
_logger.Trace("Finding quality. Source: {0}. Resolution: {1}", source, resolution);
var quality = new QualityModel(QualityFinder.FindBySourceAndResolution(source, resolution), revison);
if (resolutionConfidence == Confidence.MediaInfo)
{
quality.QualityDetectionSource = QualityDetectionSource.MediaInfo;
}
else if (sourceConfidence == Confidence.Fallback || resolutionConfidence == Confidence.Fallback)
{
quality.QualityDetectionSource = QualityDetectionSource.Extension;
}
else
{
quality.QualityDetectionSource = QualityDetectionSource.Name;
}
_logger.Debug("Using quality: {0}", quality);
localMovie.Quality = quality;
return localMovie;
}
}
}

@ -0,0 +1,27 @@
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators
{
public class AggregateReleaseGroup : IAggregateLocalMovie
{
public LocalMovie Aggregate(LocalMovie localMovie, bool otherFiles)
{
var releaseGroup = localMovie.DownloadClientMovieInfo?.ReleaseGroup;
if (releaseGroup.IsNullOrWhiteSpace())
{
releaseGroup = localMovie.FolderMovieInfo?.ReleaseGroup;
}
if (releaseGroup.IsNullOrWhiteSpace())
{
releaseGroup = localMovie.FileMovieInfo?.ReleaseGroup;
}
localMovie.ReleaseGroup = releaseGroup;
return localMovie;
}
}
}

@ -0,0 +1,23 @@
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenters.Quality
{
public class AugmentQualityFromDownloadClientItem : IAugmentQuality
{
public AugmentQualityResult AugmentQuality(LocalMovie localMovie)
{
var quality = localMovie.DownloadClientMovieInfo?.Quality;
if (quality == null)
{
return null;
}
return new AugmentQualityResult(quality.Quality.Source,
Confidence.Tag,
quality.Quality.Resolution,
Confidence.Tag,
quality.Revision);
}
}
}

@ -0,0 +1,28 @@
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenters.Quality
{
public class AugmentQualityFromFileName : IAugmentQuality
{
public AugmentQualityResult AugmentQuality(LocalMovie localMovie)
{
var quality = localMovie.FileMovieInfo?.Quality;
if (quality == null)
{
return null;
}
var confidence = quality.QualityDetectionSource == QualityDetectionSource.Extension
? Confidence.Fallback
: Confidence.Tag;
return new AugmentQualityResult(quality.Quality.Source,
confidence,
quality.Quality.Resolution,
confidence,
quality.Revision);
}
}
}

@ -0,0 +1,23 @@
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenters.Quality
{
public class AugmentQualityFromFolder : IAugmentQuality
{
public AugmentQualityResult AugmentQuality(LocalMovie localMovie)
{
var quality = localMovie.FolderMovieInfo?.Quality;
if (quality == null)
{
return null;
}
return new AugmentQualityResult(quality.Quality.Source,
Confidence.Tag,
quality.Quality.Resolution,
Confidence.Tag,
quality.Revision);
}
}
}

@ -0,0 +1,40 @@
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenters.Quality
{
public class AugmentQualityFromMediaInfo : IAugmentQuality
{
public AugmentQualityResult AugmentQuality(LocalMovie localMovie)
{
if (localMovie.MediaInfo == null)
{
return null;
}
var width = localMovie.MediaInfo.Width;
if (width >= 3200)
{
return AugmentQualityResult.ResolutionOnly(Resolution.R2160P, Confidence.MediaInfo);
}
if (width >= 1800)
{
return AugmentQualityResult.ResolutionOnly(Resolution.R1080P, Confidence.MediaInfo);
}
if (width >= 1200)
{
return AugmentQualityResult.ResolutionOnly(Resolution.R720P, Confidence.MediaInfo);
}
if (width > 0)
{
return AugmentQualityResult.ResolutionOnly(Resolution.R480P, Confidence.MediaInfo);
}
return null;
}
}
}

@ -0,0 +1,37 @@
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenters.Quality
{
public class AugmentQualityResult
{
public Source Source { get; set; }
public Confidence SourceConfidence { get; set; }
public Resolution Resolution { get; set; }
public Confidence ResolutionConfidence { get; set; }
public Revision Revision { get; set; }
public AugmentQualityResult(Source source,
Confidence sourceConfidence,
Resolution resolution,
Confidence resolutionConfidence,
Revision revision)
{
Source = source;
SourceConfidence = sourceConfidence;
Resolution = resolution;
ResolutionConfidence = resolutionConfidence;
Revision = revision;
}
public static AugmentQualityResult SourceOnly(Source source, Confidence sourceConfidence)
{
return new AugmentQualityResult(source, sourceConfidence, 0, Confidence.Default, null);
}
public static AugmentQualityResult ResolutionOnly(Resolution resolution, Confidence resolutionConfidence)
{
return new AugmentQualityResult(Source.UNKNOWN, Confidence.Default, resolution, resolutionConfidence, null);
}
}
}

@ -0,0 +1,10 @@
namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenters.Quality
{
public enum Confidence
{
Fallback,
Default,
Tag,
MediaInfo
}
}

@ -0,0 +1,9 @@
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenters.Quality
{
public interface IAugmentQuality
{
AugmentQualityResult AugmentQuality(LocalMovie localMovie);
}
}

@ -0,0 +1,9 @@
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators
{
public interface IAggregateLocalMovie
{
LocalMovie Aggregate(LocalMovie localMovie, bool otherFiles);
}
}

@ -1,17 +1,14 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using NLog; using NLog;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.MediaFiles.MediaInfo; using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Movies; using NzbDrone.Core.Movies;
namespace NzbDrone.Core.MediaFiles.MovieImport namespace NzbDrone.Core.MediaFiles.MovieImport
{ {
public interface IDetectSample public interface IDetectSample
{ {
bool IsSample(Movie movie, QualityModel quality, string path, long size, bool isSpecial); DetectSampleResult IsSample(Movie movie, string path, bool isSpecial);
} }
public class DetectSample : IDetectSample public class DetectSample : IDetectSample
@ -19,23 +16,18 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
private readonly IVideoFileInfoReader _videoFileInfoReader; private readonly IVideoFileInfoReader _videoFileInfoReader;
private readonly Logger _logger; private readonly Logger _logger;
//private static List<Quality> _largeSampleSizeQualities = new List<Quality> { Quality.HDTV1080p, Quality.WEBDL1080p, Quality.Bluray1080p };
private static List<Resolution> _largeSampleSizeResolutions = new List<Resolution>{Resolution.R1080P, Resolution.R2160P};
public DetectSample(IVideoFileInfoReader videoFileInfoReader, Logger logger) public DetectSample(IVideoFileInfoReader videoFileInfoReader, Logger logger)
{ {
_videoFileInfoReader = videoFileInfoReader; _videoFileInfoReader = videoFileInfoReader;
_logger = logger; _logger = logger;
} }
public static long SampleSizeLimit => 70.Megabytes(); public DetectSampleResult IsSample(Movie movie, string path, bool isSpecial)
public bool IsSample(Movie movie, QualityModel quality, string path, long size, bool isSpecial)
{ {
if (isSpecial) if (isSpecial)
{ {
_logger.Debug("Special, skipping sample check"); _logger.Debug("Special, skipping sample check");
return false; return DetectSampleResult.NotSample;
} }
var extension = Path.GetExtension(path); var extension = Path.GetExtension(path);
@ -43,72 +35,64 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
if (extension != null && extension.Equals(".flv", StringComparison.InvariantCultureIgnoreCase)) if (extension != null && extension.Equals(".flv", StringComparison.InvariantCultureIgnoreCase))
{ {
_logger.Debug("Skipping sample check for .flv file"); _logger.Debug("Skipping sample check for .flv file");
return false; return DetectSampleResult.NotSample;
} }
if (extension != null && extension.Equals(".strm", StringComparison.InvariantCultureIgnoreCase)) if (extension != null && extension.Equals(".strm", StringComparison.InvariantCultureIgnoreCase))
{ {
_logger.Debug("Skipping sample check for .strm file"); _logger.Debug("Skipping sample check for .strm file");
return false; return DetectSampleResult.NotSample;
} }
try // TODO: Use MediaInfo from the import process, no need to re-process the file again here
var runTime = _videoFileInfoReader.GetRunTime(path);
if (!runTime.HasValue)
{ {
var runTime = _videoFileInfoReader.GetRunTime(path); _logger.Error("Failed to get runtime from the file, make sure mediainfo is available");
var minimumRuntime = GetMinimumAllowedRuntime(movie); return DetectSampleResult.Indeterminate;
if (runTime.Value.TotalMinutes.Equals(0))
{
_logger.Error("[{0}] has a runtime of 0, is it a valid video file?", path);
return true;
}
if (runTime.Value.TotalSeconds < minimumRuntime)
{
_logger.Debug("[{0}] appears to be a sample. Runtime: {1} seconds. Expected at least: {2} seconds", path, runTime, minimumRuntime);
return true;
}
} }
catch (DllNotFoundException) var minimumRuntime = GetMinimumAllowedRuntime(movie);
if (runTime.Value.TotalMinutes.Equals(0))
{ {
_logger.Debug("Falling back to file size detection"); _logger.Error("[{0}] has a runtime of 0, is it a valid video file?", path);
return DetectSampleResult.Sample;
}
return CheckSize(size, quality); if (runTime.Value.TotalSeconds < minimumRuntime)
{
_logger.Debug("[{0}] appears to be a sample. Runtime: {1} seconds. Expected at least: {2} seconds", path, runTime, minimumRuntime);
return DetectSampleResult.Sample;
} }
_logger.Debug("Runtime is over 90 seconds"); _logger.Debug("Runtime is over 90 seconds");
return false; return DetectSampleResult.NotSample;
} }
private bool CheckSize(long size, QualityModel quality) private int GetMinimumAllowedRuntime(Movie movie)
{ {
if (_largeSampleSizeResolutions.Contains(quality.Resolution)) //Anime short - 15 seconds
if (movie.Runtime <= 3)
{ {
if (size < SampleSizeLimit * 2) return 15;
{
_logger.Debug("1080p file is less than sample limit");
return true;
}
} }
if (size < SampleSizeLimit) //Webisodes - 90 seconds
if (movie.Runtime <= 10)
{ {
_logger.Debug("File is less than sample limit"); return 90;
return true;
} }
return false; //30 minute episodes - 5 minutes
} if (movie.Runtime <= 30)
private int GetMinimumAllowedRuntime(Movie movie)
{
if (movie.Runtime < 1)
{ {
return 5 * 60; return 300;
} }
return movie.Runtime / 5 * 60; //60 minute episodes - 10 minutes
return 600;
} }
} }
} }

@ -0,0 +1,9 @@
namespace NzbDrone.Core.MediaFiles.MovieImport
{
public enum DetectSampleResult
{
Indeterminate,
Sample,
NotSample
}
}

@ -85,8 +85,8 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
movieFile.Languages = localMovie.Languages; movieFile.Languages = localMovie.Languages;
movieFile.MediaInfo = localMovie.MediaInfo; movieFile.MediaInfo = localMovie.MediaInfo;
movieFile.Movie = localMovie.Movie; movieFile.Movie = localMovie.Movie;
movieFile.ReleaseGroup = localMovie.ParsedMovieInfo?.ReleaseGroup; movieFile.ReleaseGroup = localMovie.ReleaseGroup;
movieFile.Edition = localMovie.ParsedMovieInfo?.Edition; movieFile.Edition = localMovie.Edition;
bool copyOnly; bool copyOnly;
switch (importMode) switch (importMode)

@ -15,6 +15,7 @@ using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Movies; using NzbDrone.Core.Movies;
using NzbDrone.Core.MediaFiles.MediaInfo; using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.MediaFiles.MovieImport.Aggregation;
namespace NzbDrone.Core.MediaFiles.MovieImport namespace NzbDrone.Core.MediaFiles.MovieImport
@ -22,18 +23,15 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
public interface IMakeImportDecision public interface IMakeImportDecision
{ {
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie); List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie);
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, bool shouldCheckQuality);
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource, bool shouldCheckQuality);
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource); List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource);
} }
public class ImportDecisionMaker : IMakeImportDecision public class ImportDecisionMaker : IMakeImportDecision
{ {
private readonly IEnumerable<IImportDecisionEngineSpecification> _specifications; private readonly IEnumerable<IImportDecisionEngineSpecification> _specifications;
private readonly IParsingService _parsingService;
private readonly IMediaFileService _mediaFileService; private readonly IMediaFileService _mediaFileService;
private readonly IAggregationService _aggregationService;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IVideoFileInfoReader _videoFileInfoReader;
private readonly IDetectSample _detectSample; private readonly IDetectSample _detectSample;
private readonly IQualityDefinitionService _qualitiesService; private readonly IQualityDefinitionService _qualitiesService;
private readonly IConfigService _config; private readonly IConfigService _config;
@ -42,10 +40,9 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
private readonly Logger _logger; private readonly Logger _logger;
public ImportDecisionMaker(IEnumerable<IImportDecisionEngineSpecification> specifications, public ImportDecisionMaker(IEnumerable<IImportDecisionEngineSpecification> specifications,
IParsingService parsingService,
IMediaFileService mediaFileService, IMediaFileService mediaFileService,
IAggregationService aggregationService,
IDiskProvider diskProvider, IDiskProvider diskProvider,
IVideoFileInfoReader videoFileInfoReader,
IDetectSample detectSample, IDetectSample detectSample,
IQualityDefinitionService qualitiesService, IQualityDefinitionService qualitiesService,
IConfigService config, IConfigService config,
@ -54,10 +51,9 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
Logger logger) Logger logger)
{ {
_specifications = specifications; _specifications = specifications;
_parsingService = parsingService;
_mediaFileService = mediaFileService; _mediaFileService = mediaFileService;
_aggregationService = aggregationService;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_videoFileInfoReader = videoFileInfoReader;
_detectSample = detectSample; _detectSample = detectSample;
_qualitiesService = qualitiesService; _qualitiesService = qualitiesService;
_config = config; _config = config;
@ -68,12 +64,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie) public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie)
{ {
return GetImportDecisions(videoFiles, movie, null, null, true, false); return GetImportDecisions(videoFiles, movie, null, null, true);
}
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, bool shouldCheckQuality = false)
{
return GetImportDecisions(videoFiles, movie, null, null, true, shouldCheckQuality);
} }
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource) public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource)
@ -82,102 +73,79 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
_logger.Debug("Analyzing {0}/{1} files.", newFiles.Count, videoFiles.Count()); _logger.Debug("Analyzing {0}/{1} files.", newFiles.Count, videoFiles.Count());
var shouldUseFolderName = ShouldUseFolderName(videoFiles, movie, folderInfo); ParsedMovieInfo downloadClientItemInfo = null;
var decisions = new List<ImportDecision>();
foreach (var file in newFiles) if (downloadClientItem != null)
{ {
decisions.AddIfNotNull(GetDecision(file, movie, downloadClientItem, folderInfo, sceneSource, shouldUseFolderName)); downloadClientItemInfo = Parser.Parser.ParseMovieTitle(downloadClientItem.Title, false);
} }
return decisions; var nonSampleVideoFileCount = GetNonSampleVideoFileCount(newFiles, movie, downloadClientItemInfo, folderInfo);
}
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource, bool shouldCheckQuality)
{
var newFiles = _mediaFileService.FilterExistingFiles(videoFiles.ToList(), movie);
_logger.Debug("Analyzing {0}/{1} files.", newFiles.Count, videoFiles.Count());
var shouldUseFolderName = ShouldUseFolderName(videoFiles, movie, folderInfo);
var decisions = new List<ImportDecision>(); var decisions = new List<ImportDecision>();
foreach (var file in newFiles) foreach (var file in newFiles)
{ {
decisions.AddIfNotNull(GetDecision(file, movie, downloadClientItem, folderInfo, sceneSource, shouldUseFolderName, shouldCheckQuality)); var localMovie = new LocalMovie
{
Movie = movie,
DownloadClientMovieInfo = downloadClientItemInfo,
FolderMovieInfo = folderInfo,
Path = file,
SceneSource = sceneSource
};
decisions.AddIfNotNull(GetDecision(localMovie, downloadClientItem, nonSampleVideoFileCount > 1));
} }
return decisions; return decisions;
} }
private ImportDecision GetDecision(string file, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource, bool shouldUseFolderName, bool shouldCheckQuality = false) private ImportDecision GetDecision(LocalMovie localMovie, DownloadClientItem downloadClientItem, bool otherFiles)
{ {
ImportDecision decision = null; ImportDecision decision = null;
try var fileMovieInfo = Parser.Parser.ParseMoviePath(localMovie.Path, false);
{
ParsedMovieInfo modifiedFolderInfo = null;
if (folderInfo != null)
{
modifiedFolderInfo = folderInfo.JsonClone();
// We want the filename to be used for parsing quality, etc. even if we didn't get any movie info from there.
modifiedFolderInfo.SimpleReleaseTitle = Path.GetFileName(file);
}
var minimalInfo = _parsingService.ParseMinimalPathMovieInfo(file) ?? modifiedFolderInfo; localMovie.FileMovieInfo = fileMovieInfo;
localMovie.Size = _diskProvider.GetFileSize(localMovie.Path);
LocalMovie localMovie = null; try
{
_aggregationService.Augment(localMovie, otherFiles);
if (minimalInfo != null) if (localMovie.Movie == null)
{ {
//TODO: make it so media info doesn't ruin the import process of a new movie decision = new ImportDecision(localMovie, new Rejection("Invalid movie"));
var mediaInfo = (_config.EnableMediaInfo || !movie.Path?.IsParentPath(file) == true) ? _videoFileInfoReader.GetMediaInfo(file) : null;
var size = _diskProvider.GetFileSize(file);
var historyItems = _historyService.FindByDownloadId(downloadClientItem?.DownloadId ?? "");
var firstHistoryItem = historyItems?.OrderByDescending(h => h.Date)?.FirstOrDefault();
var sizeMovie = new LocalMovie();
sizeMovie.Size = size;
localMovie = _parsingService.GetLocalMovie(file, minimalInfo, movie, new List<object>{mediaInfo, firstHistoryItem, sizeMovie, folderInfo}, sceneSource);
localMovie.Quality = GetQuality(folderInfo, localMovie.Quality, movie);
localMovie.Size = size;
_logger.Debug("Size: {0}", localMovie.Size);
decision = GetDecision(localMovie, downloadClientItem);
} }
else else
{ {
localMovie = new LocalMovie(); decision = GetDecision(localMovie, downloadClientItem);
localMovie.Path = file;
if (MediaFileExtensions.Extensions.Contains(Path.GetExtension(file)))
{
if (_warnedFiles.Find(file) == null)
{
_warnedFiles.Set(file, "warned");
_logger.Warn("Unable to parse movie info from path {0}", file);
}
else
{
_logger.Trace("Already warned user that we are unable to parse movie info from path: {0}", file);
}
}
decision = new ImportDecision(localMovie, new Rejection("Unable to parse file"));
} }
} }
catch (Exception e) catch (AugmentingFailedException)
{
decision = new ImportDecision(localMovie, new Rejection("Unable to parse file"));
}
catch (Exception ex)
{ {
_logger.Error(e, "Couldn't import file. " + file); _logger.Error(ex, "Couldn't import file. {0}", localMovie.Path);
var localMovie = new LocalMovie { Path = file };
decision = new ImportDecision(localMovie, new Rejection("Unexpected error processing file")); decision = new ImportDecision(localMovie, new Rejection("Unexpected error processing file"));
} }
//LocalMovie nullMovie = null; if (decision == null)
{
//decision = new ImportDecision(nullMovie, new Rejection("IMPLEMENTATION MISSING!!!")); _logger.Error("Unable to make a decision on {0}", localMovie.Path);
}
else if (decision.Rejections.Any())
{
_logger.Debug("File rejected for the following reasons: {0}", string.Join(", ", decision.Rejections));
}
else
{
_logger.Debug("File accepted");
}
return decision; return decision;
} }
@ -217,66 +185,19 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
return null; return null;
} }
//TODO: Remove this method, since it is no longer needed. private int GetNonSampleVideoFileCount(List<string> videoFiles, Movie movie, ParsedMovieInfo downloadClientItemInfo, ParsedMovieInfo folderInfo)
private bool ShouldUseFolderName(List<string> videoFiles, Movie movie, ParsedMovieInfo folderInfo)
{
return false;
}
private QualityModel GetQuality(ParsedMovieInfo folderInfo, QualityModel fileQuality, Movie movie)
{
if (UseFolderQuality(folderInfo, fileQuality, movie))
{
_logger.Debug("Using quality from folder: {0}", folderInfo.Quality);
return folderInfo.Quality;
}
return fileQuality;
}
private bool UseFolderQuality(ParsedMovieInfo folderInfo, QualityModel fileQuality, Movie movie)
{ {
if (folderInfo == null) return videoFiles.Count(file =>
{
return false;
}
if (folderInfo.Quality.Quality == Quality.Unknown)
{ {
return false; var sample = _detectSample.IsSample(movie, file, false);
}
if (fileQuality.QualitySource == QualitySource.Extension)
{
return true;
}
if (fileQuality.QualitySource == QualitySource.MediaInfo) if (sample == DetectSampleResult.Sample)
{ {
return false; return false;
} }
if (new QualityModelComparer(movie.Profile).Compare(folderInfo.Quality, fileQuality) > 0)
{
return true; return true;
} });
return false;
} }
private bool ShouldCheckQualityForParsedQuality(Quality quality)
{
List<Quality> shouldNotCheck = new List<Quality> { Quality.WORKPRINT, Quality.TELECINE, Quality.TELESYNC,
Quality.DVDSCR, Quality.DVD, Quality.CAM, Quality.DVDR, Quality.Remux1080p, Quality.Remux2160p, Quality.REGIONAL
};
if (shouldNotCheck.Contains(quality))
{
return false;
}
return true;
}
} }
} }

@ -17,6 +17,7 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Movies; using NzbDrone.Core.Movies;
using NzbDrone.Core.MediaFiles.MovieImport.Aggregation;
namespace NzbDrone.Core.MediaFiles.MovieImport.Manual namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
{ {
@ -32,8 +33,8 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
private readonly IDiskScanService _diskScanService; private readonly IDiskScanService _diskScanService;
private readonly IMakeImportDecision _importDecisionMaker; private readonly IMakeImportDecision _importDecisionMaker;
private readonly IMovieService _movieService; private readonly IMovieService _movieService;
private readonly IVideoFileInfoReader _videoFileInfoReader;
private readonly IImportApprovedMovie _importApprovedMovie; private readonly IImportApprovedMovie _importApprovedMovie;
private readonly IAggregationService _aggregationService;
private readonly ITrackedDownloadService _trackedDownloadService; private readonly ITrackedDownloadService _trackedDownloadService;
private readonly IDownloadedMovieImportService _downloadedMovieImportService; private readonly IDownloadedMovieImportService _downloadedMovieImportService;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
@ -46,7 +47,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
IDiskScanService diskScanService, IDiskScanService diskScanService,
IMakeImportDecision importDecisionMaker, IMakeImportDecision importDecisionMaker,
IMovieService movieService, IMovieService movieService,
IVideoFileInfoReader videoFileInfoReader, IAggregationService aggregationService,
IImportApprovedMovie importApprovedMovie, IImportApprovedMovie importApprovedMovie,
ITrackedDownloadService trackedDownloadService, ITrackedDownloadService trackedDownloadService,
IDownloadedMovieImportService downloadedMovieImportService, IDownloadedMovieImportService downloadedMovieImportService,
@ -60,7 +61,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
_diskScanService = diskScanService; _diskScanService = diskScanService;
_importDecisionMaker = importDecisionMaker; _importDecisionMaker = importDecisionMaker;
_movieService = movieService; _movieService = movieService;
_videoFileInfoReader = videoFileInfoReader; _aggregationService = aggregationService;
_importApprovedMovie = importApprovedMovie; _importApprovedMovie = importApprovedMovie;
_trackedDownloadService = trackedDownloadService; _trackedDownloadService = trackedDownloadService;
_downloadedMovieImportService = downloadedMovieImportService; _downloadedMovieImportService = downloadedMovieImportService;
@ -91,16 +92,17 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
return new List<ManualImportItem>(); return new List<ManualImportItem>();
} }
return new List<ManualImportItem> { ProcessFile(path, downloadId) }; var rootFolder = Path.GetDirectoryName(path);
return new List<ManualImportItem> { ProcessFile(rootFolder, rootFolder, path, downloadId) };
} }
return ProcessFolder(path, downloadId, filterExistingFiles); return ProcessFolder(path, path, downloadId, filterExistingFiles);
} }
private List<ManualImportItem> ProcessFolder(string folder, string downloadId, bool filterExistingFiles) private List<ManualImportItem> ProcessFolder(string rootFolder, string baseFolder, string downloadId, bool filterExistingFiles)
{ {
DownloadClientItem downloadClientItem = null; DownloadClientItem downloadClientItem = null;
var directoryInfo = new DirectoryInfo(folder); var directoryInfo = new DirectoryInfo(baseFolder);
var movie = _parsingService.GetMovie(directoryInfo.Name); var movie = _parsingService.GetMovie(directoryInfo.Name);
if (downloadId.IsNotNullOrWhiteSpace()) if (downloadId.IsNotNullOrWhiteSpace())
@ -114,33 +116,40 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
} }
} }
// Try a lookup by the path if the movie is still unknown, this will handle
// the case where the movie folder doesn't match the movie title.
if (movie == null) if (movie == null)
{ {
var files = _diskScanService.FilterFiles(folder, _diskScanService.GetVideoFiles(folder)); movie = _movieService.FindByPath(rootFolder);
}
if (movie == null)
{
var files = _diskScanService.FilterFiles(baseFolder, _diskScanService.GetVideoFiles(baseFolder, false));
var subfolders = _diskScanService.FilterFiles(baseFolder, _diskProvider.GetDirectories(baseFolder));
return files.Select(file => ProcessFile(file, downloadId, folder)).Where(i => i != null).ToList(); var processedFiles = files.Select(file => ProcessFile(rootFolder, baseFolder, file, downloadId));
var processedFolders = subfolders.SelectMany(subfolder => ProcessFolder(rootFolder, subfolder, downloadId, filterExistingFiles));
return processedFiles.Concat(processedFolders).Where(i => i != null).ToList();
} }
var historyItems = _historyService.FindByDownloadId(downloadId); var folderInfo = Parser.Parser.ParseMovieTitle(directoryInfo.Name, false);
var firstHistoryItem = historyItems.OrderByDescending(h => h.Date).FirstOrDefault(); var movieFiles = _diskScanService.GetVideoFiles(baseFolder).ToList();
var folderInfo = _parsingService.ParseMovieInfo(directoryInfo.Name, new List<object>{firstHistoryItem}); var decisions = _importDecisionMaker.GetImportDecisions(movieFiles, movie, downloadClientItem, folderInfo, SceneSource(movie, baseFolder));
var movieFiles = _diskScanService.GetVideoFiles(folder).ToList();
var decisions = _importDecisionMaker.GetImportDecisions(movieFiles, movie, downloadClientItem, folderInfo, SceneSource(movie, folder), false);
return decisions.Select(decision => MapItem(decision, folder, downloadId, directoryInfo.Name)).ToList(); return decisions.Select(decision => MapItem(decision, rootFolder, downloadId, directoryInfo.Name)).ToList();
} }
private ManualImportItem ProcessFile(string file, string downloadId, string folder = null) private ManualImportItem ProcessFile(string rootFolder, string baseFolder, string file, string downloadId, Movie movie = null)
{ {
if (folder.IsNullOrWhiteSpace())
{
folder = new FileInfo(file).Directory.FullName;
}
DownloadClientItem downloadClientItem = null; DownloadClientItem downloadClientItem = null;
var relativeFile = folder.GetRelativePath(file); var relativeFile = baseFolder.GetRelativePath(file);
var movie = _parsingService.GetMovie(relativeFile.Split('\\', '/')[0]); if (movie == null)
{
_parsingService.GetMovie(relativeFile.Split('\\', '/')[0]);
}
if (movie == null) if (movie == null)
{ {
@ -160,34 +169,39 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
if (movie == null) if (movie == null)
{ {
var localMovie = new LocalMovie() var relativeParseInfo = Parser.Parser.ParseMoviePath(relativeFile, false);
if (relativeParseInfo != null)
{ {
Path = file, movie = _movieService.FindByTitle(relativeParseInfo.MovieTitle);
Quality = QualityParser.ParseQuality(file), }
Size = _diskProvider.GetFileSize(file) }
};
return MapItem(new ImportDecision(localMovie, new Rejection("Unknown Movie")), folder, downloadId, null); if (movie == null)
{
var localMovie = new LocalMovie();
localMovie.Path = file;
localMovie.Quality = QualityParser.ParseQuality(file);
localMovie.Languages = LanguageParser.ParseLanguages(file);
localMovie.Size = _diskProvider.GetFileSize(file);
return MapItem(new ImportDecision(localMovie, new Rejection("Unknown Movie")), rootFolder, downloadId, null);
} }
var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> { file }, var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> {file}, movie, downloadClientItem, null, SceneSource(movie, baseFolder));
movie, downloadClientItem, null, SceneSource(movie, folder), true);
if (importDecisions.Any()) if (importDecisions.Any())
{ {
return MapItem(importDecisions.First(), folder, downloadId, null); return MapItem(importDecisions.First(), rootFolder, downloadId, null);
} }
return new ManualImportItem return new ManualImportItem
{ {
DownloadId = downloadId, DownloadId = downloadId,
Path = file, Path = file,
RelativePath = folder.GetRelativePath(file), RelativePath = rootFolder.GetRelativePath(file),
Name = Path.GetFileNameWithoutExtension(file), Name = Path.GetFileNameWithoutExtension(file),
Rejections = new List<Rejection> Rejections = new List<Rejection>()
{
new Rejection("Unable to process file")
}
}; };
} }
@ -232,15 +246,14 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
var file = message.Files[i]; var file = message.Files[i];
var movie = _movieService.GetMovie(file.MovieId); var movie = _movieService.GetMovie(file.MovieId);
var parsedMovieInfo = _parsingService.ParseMoviePathInfo(file.Path, new List<object>()) ?? new ParsedMovieInfo(); var fileMovieInfo = Parser.Parser.ParseMoviePath(file.Path, false) ?? new ParsedMovieInfo();
var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path);
var existingFile = movie.Path.IsParentPath(file.Path); var existingFile = movie.Path.IsParentPath(file.Path);
TrackedDownload trackedDownload = null;
var localMovie = new LocalMovie var localMovie = new LocalMovie
{ {
ExistingFile = false, ExistingFile = false,
MediaInfo = mediaInfo, FileMovieInfo = fileMovieInfo,
ParsedMovieInfo = parsedMovieInfo,
Path = file.Path, Path = file.Path,
Quality = file.Quality, Quality = file.Quality,
Languages = file.Languages, Languages = file.Languages,
@ -248,26 +261,42 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
Size = 0 Size = 0
}; };
if (file.DownloadId.IsNotNullOrWhiteSpace())
{
trackedDownload = _trackedDownloadService.Find(file.DownloadId);
localMovie.DownloadClientMovieInfo = trackedDownload?.RemoteMovie?.ParsedMovieInfo;
}
if (file.FolderName.IsNotNullOrWhiteSpace())
{
localMovie.FolderMovieInfo = Parser.Parser.ParseMovieTitle(file.FolderName, false);
}
localMovie = _aggregationService.Augment(localMovie, false);
// Apply the user-chosen values.
localMovie.Movie = movie;
localMovie.Quality = file.Quality;
//TODO: Cleanup non-tracked downloads //TODO: Cleanup non-tracked downloads
var importDecision = new ImportDecision(localMovie); var importDecision = new ImportDecision(localMovie);
if (file.DownloadId.IsNullOrWhiteSpace()) if (trackedDownload == null)
{ {
imported.AddRange(_importApprovedMovie.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode)); imported.AddRange(_importApprovedMovie.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
} }
else else
{ {
var trackedDownload = _trackedDownloadService.Find(file.DownloadId);
var importResult = _importApprovedMovie.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First(); var importResult = _importApprovedMovie.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
imported.Add(importResult); imported.Add(importResult);
importedTrackedDownload.Add(new ManuallyImportedFile importedTrackedDownload.Add(new ManuallyImportedFile
{ {
TrackedDownload = trackedDownload, TrackedDownload = trackedDownload,
ImportResult = importResult ImportResult = importResult
}); });
} }
} }

@ -17,15 +17,13 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Specifications
_logger = logger; _logger = logger;
} }
public Decision IsSatisfiedBy(LocalMovie localEpisode, DownloadClientItem downloadClientItem) public Decision IsSatisfiedBy(LocalMovie localMovie, DownloadClientItem downloadClientItem)
{ {
var sample = _detectSample.IsSample(localEpisode.Movie, var sample = _detectSample.IsSample(localMovie.Movie,
localEpisode.Quality, localMovie.Path,
localEpisode.Path,
localEpisode.Size,
false); false);
if (sample) if (sample == DetectSampleResult.Sample)
{ {
return Decision.Reject("Sample"); return Decision.Reject("Sample");
} }

@ -27,6 +27,7 @@ namespace NzbDrone.Core.Movies
List<Movie> GetMoviesByFileId(int fileId); List<Movie> GetMoviesByFileId(int fileId);
void SetFileId(int fileId, int movieId); void SetFileId(int fileId, int movieId);
PagingSpec<Movie> MoviesWhereCutoffUnmet(PagingSpec<Movie> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff); PagingSpec<Movie> MoviesWhereCutoffUnmet(PagingSpec<Movie> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff);
Movie FindByPath(string path);
} }
public class MovieRepository : BasicRepository<Movie>, IMovieRepository public class MovieRepository : BasicRepository<Movie>, IMovieRepository
@ -103,61 +104,6 @@ namespace NzbDrone.Core.Movies
return pagingSpec; return pagingSpec;
} }
/*public override PagingSpec<Movie> GetPaged(PagingSpec<Movie> pagingSpec)
{
if (pagingSpec.SortKey == "downloadedQuality")
{
var mapper = _database.GetDataMapper();
var offset = pagingSpec.PagingOffset();
var limit = pagingSpec.PageSize;
var direction = "ASC";
if (pagingSpec.SortDirection == NzbDrone.Core.Datastore.SortDirection.Descending)
{
direction = "DESC";
}
var q = mapper.Query<Movie>($"SELECT * from \"Movies\" , \"MovieFiles\", \"QualityDefinitions\" WHERE Movies.MovieFileId=MovieFiles.Id AND instr(MovieFiles.Quality, ('quality\": ' || QualityDefinitions.Quality || \",\")) > 0 ORDER BY QualityDefinitions.Title {direction} LIMIT {offset},{limit};");
var q2 = mapper.Query<Movie>("SELECT * from \"Movies\" , \"MovieFiles\", \"QualityDefinitions\" WHERE Movies.MovieFileId=MovieFiles.Id AND instr(MovieFiles.Quality, ('quality\": ' || QualityDefinitions.Quality || \",\")) > 0 ORDER BY QualityDefinitions.Title ASC;");
//var ok = q.BuildQuery();
var q3 = Query.OrderBy("json_extract([t2].[quality], '$.quality') DESC");
pagingSpec.Records = q3.ToList();
pagingSpec.TotalRecords = q3.GetRowCount();
}
else
{
pagingSpec = base.GetPaged(pagingSpec);
//pagingSpec.Records = GetPagedQuery(Query, pagingSpec).ToList();
//pagingSpec.TotalRecords = GetPagedQuery(Query, pagingSpec).GetRowCount();
}
if (pagingSpec.Records.Count == 0 && pagingSpec.Page != 1)
{
var lastPossiblePage = pagingSpec.TotalRecords / pagingSpec.PageSize + 1;
pagingSpec.Page = lastPossiblePage;
return GetPaged(pagingSpec);
}
return pagingSpec;
}*/
/*protected override SortBuilder<Movie> GetPagedQuery(QueryBuilder<Movie> query, PagingSpec<Movie> pagingSpec)
{
return DataMapper.Query<Movie>().Join<Movie, AlternativeTitle>(JoinType.Left, m => m.AlternativeTitles,
(m, t) => m.Id == t.MovieId).Where(pagingSpec.FilterExpression)
.OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection())
.Skip(pagingSpec.PagingOffset())
.Take(pagingSpec.PageSize);
}*/
/*protected override SortBuilder<Movie> GetPagedQuery(QueryBuilder<Movie> query, PagingSpec<Movie> pagingSpec)
{
var newQuery = base.GetPagedQuery(query.Join<Movie, AlternativeTitle>(JoinType.Left, m => m.JoinAlternativeTitles, (movie, title) => title.MovieId == movie.Id), pagingSpec);
System.Console.WriteLine(newQuery.ToString());
return newQuery;
}*/
public SortBuilder<Movie> GetMoviesWithoutFilesQuery(PagingSpec<Movie> pagingSpec) public SortBuilder<Movie> GetMoviesWithoutFilesQuery(PagingSpec<Movie> pagingSpec)
{ {
return Query.Where(pagingSpec.FilterExpressions.FirstOrDefault()) return Query.Where(pagingSpec.FilterExpressions.FirstOrDefault())
@ -242,15 +188,6 @@ namespace NzbDrone.Core.Movies
if (result == null) if (result == null)
{ {
/*IEnumerable<Movie> movies = All();
Func<string, string> titleCleaner = title => CoreParser.CleanSeriesTitle(title.ToLower());
Func<IEnumerable<AlternativeTitle>, string, bool> altTitleComparer =
(alternativeTitles, atitle) =>
alternativeTitles.Any(altTitle => altTitle.CleanTitle == atitle);*/
/*result = movies.Where(m => altTitleComparer(m.AlternativeTitles, cleanTitle) ||
altTitleComparer(m.AlternativeTitles, cleanTitleWithRomanNumbers) ||
altTitleComparer(m.AlternativeTitles, cleanTitleWithArabicNumbers)).FirstWithYear(year);*/
result = Query.Join<Movie, AlternativeTitle>(JoinType.Inner, m => m.AlternativeTitles, (m, t) => m.Id == t.MovieId) result = Query.Join<Movie, AlternativeTitle>(JoinType.Inner, m => m.AlternativeTitles, (m, t) => m.Id == t.MovieId)
.Where(t => t.CleanTitle == cleanTitle || t.CleanTitle == cleanTitleWithArabicNumbers || t.CleanTitle == cleanTitleWithRomanNumbers) .Where(t => t.CleanTitle == cleanTitle || t.CleanTitle == cleanTitleWithArabicNumbers || t.CleanTitle == cleanTitleWithRomanNumbers)
@ -260,12 +197,6 @@ namespace NzbDrone.Core.Movies
} }
return result; return result;
/*return year.HasValue
? results?.FirstOrDefault(movie => movie.Year == year.Value)
: results?.FirstOrDefault();*/
} }
public Movie FindByTmdbId(int tmdbid) public Movie FindByTmdbId(int tmdbid)
@ -273,6 +204,12 @@ namespace NzbDrone.Core.Movies
return Query.Where(m => m.TmdbId == tmdbid).FirstOrDefault(); return Query.Where(m => m.TmdbId == tmdbid).FirstOrDefault();
} }
public Movie FindByPath(string path)
{
return Query.Where(s => s.Path == path)
.FirstOrDefault();
}
protected override QueryBuilder<TActual> AddJoinQueries<TActual>(QueryBuilder<TActual> baseQuery) protected override QueryBuilder<TActual> AddJoinQueries<TActual>(QueryBuilder<TActual> baseQuery)
{ {
baseQuery = base.AddJoinQueries(baseQuery); baseQuery = base.AddJoinQueries(baseQuery);

@ -30,6 +30,7 @@ namespace NzbDrone.Core.Movies
Movie FindByTitle(string title, int year); Movie FindByTitle(string title, int year);
Movie FindByTitleInexact(string title, int? year); Movie FindByTitleInexact(string title, int? year);
Movie FindByTitleSlug(string slug); Movie FindByTitleSlug(string slug);
Movie FindByPath(string path);
bool MovieExists(Movie movie); bool MovieExists(Movie movie);
Movie GetMovieByFileId(int fileId); Movie GetMovieByFileId(int fileId);
List<Movie> GetMoviesBetweenDates(DateTime start, DateTime end, bool includeUnmonitored); List<Movie> GetMoviesBetweenDates(DateTime start, DateTime end, bool includeUnmonitored);
@ -287,6 +288,11 @@ namespace NzbDrone.Core.Movies
return _movieRepository.FindByTitle(title.CleanSeriesTitle(), year); return _movieRepository.FindByTitle(title.CleanSeriesTitle(), year);
} }
public Movie FindByPath(string path)
{
return _movieRepository.FindByPath(path);
}
public void DeleteMovie(int movieId, bool deleteFiles, bool addExclusion = false) public void DeleteMovie(int movieId, bool deleteFiles, bool addExclusion = false)
{ {
var movie = _movieRepository.Get(movieId); var movie = _movieRepository.Get(movieId);

@ -205,6 +205,20 @@
<Compile Include="MediaCover\EnsureMediaCoversCommand.cs" /> <Compile Include="MediaCover\EnsureMediaCoversCommand.cs" />
<Compile Include="MediaFiles\Commands\UpdateMovieFileQualityCommand.cs" /> <Compile Include="MediaFiles\Commands\UpdateMovieFileQualityCommand.cs" />
<Compile Include="MediaFiles\Events\MovieFileRenamedEvent.cs" /> <Compile Include="MediaFiles\Events\MovieFileRenamedEvent.cs" />
<Compile Include="MediaFiles\MovieImport\Aggregation\AggregationFailedException.cs" />
<Compile Include="MediaFiles\MovieImport\Aggregation\AggregationService.cs" />
<Compile Include="MediaFiles\MovieImport\Aggregation\Aggregators\AggregateLanguage.cs" />
<Compile Include="MediaFiles\MovieImport\Aggregation\Aggregators\AggregateQuality.cs" />
<Compile Include="MediaFiles\MovieImport\Aggregation\Aggregators\AggregateReleaseGroup.cs" />
<Compile Include="MediaFiles\MovieImport\Aggregation\Aggregators\Augmenters\Quality\AugmentQualityFromDownloadClientItem.cs" />
<Compile Include="MediaFiles\MovieImport\Aggregation\Aggregators\Augmenters\Quality\AugmentQualityFromFileName.cs" />
<Compile Include="MediaFiles\MovieImport\Aggregation\Aggregators\Augmenters\Quality\AugmentQualityFromFolder.cs" />
<Compile Include="MediaFiles\MovieImport\Aggregation\Aggregators\Augmenters\Quality\AugmentQualityFromMediaInfo.cs" />
<Compile Include="MediaFiles\MovieImport\Aggregation\Aggregators\Augmenters\Quality\AugmentQualityResult.cs" />
<Compile Include="MediaFiles\MovieImport\Aggregation\Aggregators\Augmenters\Quality\Confidence.cs" />
<Compile Include="MediaFiles\MovieImport\Aggregation\Aggregators\Augmenters\Quality\IAugmentQuality.cs" />
<Compile Include="MediaFiles\MovieImport\Aggregation\Aggregators\IAggregateLocalMovie.cs" />
<Compile Include="MediaFiles\MovieImport\DetectSampleResult.cs" />
<Compile Include="MediaFiles\MovieImport\Specifications\GrabbedReleaseQualitySpecification.cs" /> <Compile Include="MediaFiles\MovieImport\Specifications\GrabbedReleaseQualitySpecification.cs" />
<Compile Include="MediaFiles\MovieImport\Specifications\SameFileSpecification.cs" /> <Compile Include="MediaFiles\MovieImport\Specifications\SameFileSpecification.cs" />
<Compile Include="MediaFiles\Events\MovieFileUpdatedEvent.cs" /> <Compile Include="MediaFiles\Events\MovieFileUpdatedEvent.cs" />
@ -1078,6 +1092,8 @@
<Compile Include="Profiles\ProfileFormatItem.cs" /> <Compile Include="Profiles\ProfileFormatItem.cs" />
<Compile Include="Profiles\ProfileRepository.cs" /> <Compile Include="Profiles\ProfileRepository.cs" />
<Compile Include="ProgressMessaging\ProgressMessageContext.cs" /> <Compile Include="ProgressMessaging\ProgressMessageContext.cs" />
<Compile Include="Qualities\QualityDetectionSource.cs" />
<Compile Include="Qualities\QualityFinder.cs" />
<Compile Include="Qualities\QualitySource.cs" /> <Compile Include="Qualities\QualitySource.cs" />
<Compile Include="Qualities\Revision.cs" /> <Compile Include="Qualities\Revision.cs" />
<Compile Include="Queue\EstimatedCompletionTimeComparer.cs" /> <Compile Include="Queue\EstimatedCompletionTimeComparer.cs" />

@ -6,42 +6,6 @@ namespace NzbDrone.Core.Organizer
{ {
public static class FileNameValidation public static class FileNameValidation
{ {
private static readonly Regex SeasonFolderRegex = new Regex(@"(\{season(\:\d+)?\})",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
internal static readonly Regex OriginalTokenRegex = new Regex(@"(\{original[- ._](?:title|filename)\})",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static IRuleBuilderOptions<T, string> ValidEpisodeFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
{
ruleBuilder.SetValidator(new NotEmptyValidator(null));
return ruleBuilder.SetValidator(new ValidStandardEpisodeFormatValidator());
}
public static IRuleBuilderOptions<T, string> ValidDailyEpisodeFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
{
ruleBuilder.SetValidator(new NotEmptyValidator(null));
return ruleBuilder.SetValidator(new ValidDailyEpisodeFormatValidator());
}
public static IRuleBuilderOptions<T, string> ValidAnimeEpisodeFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
{
ruleBuilder.SetValidator(new NotEmptyValidator(null));
return ruleBuilder.SetValidator(new ValidAnimeEpisodeFormatValidator());
}
public static IRuleBuilderOptions<T, string> ValidSeriesFolderFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
{
ruleBuilder.SetValidator(new NotEmptyValidator(null));
return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.SeriesTitleRegex)).WithMessage("Must contain movie title");
}
public static IRuleBuilderOptions<T, string> ValidSeasonFolderFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
{
ruleBuilder.SetValidator(new NotEmptyValidator(null));
return ruleBuilder.SetValidator(new RegularExpressionValidator(SeasonFolderRegex)).WithMessage("Must contain season number");
}
public static IRuleBuilderOptions<T, string> ValidMovieFolderFormat<T>(this IRuleBuilder<T, string> ruleBuilder) public static IRuleBuilderOptions<T, string> ValidMovieFolderFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
{ {
ruleBuilder.SetValidator(new NotEmptyValidator(null)); ruleBuilder.SetValidator(new NotEmptyValidator(null));
@ -54,78 +18,4 @@ namespace NzbDrone.Core.Organizer
return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.MovieTitleRegex)).WithMessage("Must contain movie title"); return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.MovieTitleRegex)).WithMessage("Must contain movie title");
} }
} }
public class ValidStandardEpisodeFormatValidator : PropertyValidator
{
public ValidStandardEpisodeFormatValidator()
: base("Must contain season and episode numbers OR Original Title")
{
}
protected override bool IsValid(PropertyValidatorContext context)
{
var value = context.PropertyValue as string;
return true;
if (!FileNameBuilder.SeasonEpisodePatternRegex.IsMatch(value) &&
!FileNameValidation.OriginalTokenRegex.IsMatch(value))
{
return false;
}
return true;
}
}
public class ValidDailyEpisodeFormatValidator : PropertyValidator
{
public ValidDailyEpisodeFormatValidator()
: base("Must contain Air Date OR Season and Episode OR Original Title")
{
}
protected override bool IsValid(PropertyValidatorContext context)
{
var value = context.PropertyValue as string;
return true;
if (!FileNameBuilder.SeasonEpisodePatternRegex.IsMatch(value) &&
!FileNameBuilder.AirDateRegex.IsMatch(value) &&
!FileNameValidation.OriginalTokenRegex.IsMatch(value))
{
return false;
}
return true;
}
}
public class ValidAnimeEpisodeFormatValidator : PropertyValidator
{
public ValidAnimeEpisodeFormatValidator()
: base("Must contain Absolute Episode number OR Season and Episode OR Original Title")
{
}
protected override bool IsValid(PropertyValidatorContext context)
{
var value = context.PropertyValue as string;
return true;
if (!FileNameBuilder.SeasonEpisodePatternRegex.IsMatch(value) &&
!FileNameBuilder.AbsoluteEpisodePatternRegex.IsMatch(value) &&
!FileNameValidation.OriginalTokenRegex.IsMatch(value))
{
return false;
}
return true;
}
}
} }

@ -50,7 +50,7 @@ namespace NzbDrone.Core.Parser.Augmenters
if (existing != quality.Resolution) if (existing != quality.Resolution)
{ {
//_logger.Debug("Overwriting resolution info {0} with info from media info {1}", existing, quality.Resolution); //_logger.Debug("Overwriting resolution info {0} with info from media info {1}", existing, quality.Resolution);
quality.QualitySource = QualitySource.MediaInfo; quality.QualityDetectionSource = QualityDetectionSource.MediaInfo;
movieInfo.Quality = quality; movieInfo.Quality = quality;
} }
} }

@ -15,12 +15,17 @@ namespace NzbDrone.Core.Parser.Model
public string Path { get; set; } public string Path { get; set; }
public long Size { get; set; } public long Size { get; set; }
public ParsedMovieInfo ParsedMovieInfo { get; set; } public ParsedMovieInfo FileMovieInfo { get; set; }
public ParsedMovieInfo DownloadClientMovieInfo { get; set; }
public ParsedMovieInfo FolderMovieInfo { get; set; }
public Movie Movie { get; set; } public Movie Movie { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public List<Language> Languages { get; set; } public List<Language> Languages { get; set; }
public MediaInfoModel MediaInfo { get; set; } public MediaInfoModel MediaInfo { get; set; }
public bool ExistingFile { get; set; } public bool ExistingFile { get; set; }
public bool SceneSource { get; set; }
public string ReleaseGroup { get; set; }
public string Edition { get; set; }
public override string ToString() public override string ToString()

@ -28,10 +28,7 @@ namespace NzbDrone.Core.Parser.Model
/// </summary> /// </summary>
[JsonIgnore] [JsonIgnore]
public IDictionary<string, object> ExtraInfo = new Dictionary<string, object>(); public IDictionary<string, object> ExtraInfo = new Dictionary<string, object>();
//public int SeasonNumber { get; set; }
public List<Language> Languages = new List<Language>(); public List<Language> Languages = new List<Language>();
//public bool FullSeason { get; set; }
//public bool Special { get; set; }
public string ReleaseGroup { get; set; } public string ReleaseGroup { get; set; }
public string ReleaseHash { get; set; } public string ReleaseHash { get; set; }
public string Edition { get; set;} public string Edition { get; set;}

@ -149,7 +149,7 @@ namespace NzbDrone.Core.Parser
{"ü", "ue"}, {"ü", "ue"},
}; };
private static ParsedMovieInfo ParseMoviePath(string path, bool isLenient) public static ParsedMovieInfo ParseMoviePath(string path, bool isLenient)
{ {
var fileInfo = new FileInfo(path); var fileInfo = new FileInfo(path);

@ -22,11 +22,9 @@ namespace NzbDrone.Core.Parser
{ {
public interface IParsingService public interface IParsingService
{ {
LocalMovie GetLocalMovie(string filename, ParsedMovieInfo minimalInfo, Movie movie, List<object> helpers, bool sceneSource = false);
Movie GetMovie(string title); Movie GetMovie(string title);
MappingResult Map(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria = null); MappingResult Map(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria = null);
ParsedMovieInfo ParseMovieInfo(string title, List<object> helpers); ParsedMovieInfo ParseMovieInfo(string title, List<object> helpers);
ParsedMovieInfo ParseMoviePathInfo(string path, List<object> helpers);
ParsedMovieInfo ParseMinimalMovieInfo(string path, bool isDir = false); ParsedMovieInfo ParseMinimalMovieInfo(string path, bool isDir = false);
ParsedMovieInfo ParseMinimalPathMovieInfo(string path); ParsedMovieInfo ParseMinimalPathMovieInfo(string path);
List<CustomFormat> ParseCustomFormat(ParsedMovieInfo movieInfo); List<CustomFormat> ParseCustomFormat(ParsedMovieInfo movieInfo);
@ -122,27 +120,6 @@ namespace NzbDrone.Core.Parser
return minimalInfo; return minimalInfo;
} }
public ParsedMovieInfo ParseMoviePathInfo(string path, List<object> helpers)
{
var fileInfo = new FileInfo(path);
var result = ParseMovieInfo(fileInfo.Name, helpers);
if (result == null)
{
_logger.Debug("Attempting to parse movie info using directory and file names. {0}", fileInfo.Directory.Name);
result = ParseMovieInfo(fileInfo.Directory.Name + " " + fileInfo.Name, helpers);
}
if (result == null)
{
_logger.Debug("Attempting to parse movie info using directory name. {0}", fileInfo.Directory.Name);
result = ParseMovieInfo(fileInfo.Directory.Name + fileInfo.Extension, helpers);
}
return result;
}
public List<CustomFormat> ParseCustomFormat(ParsedMovieInfo movieInfo) public List<CustomFormat> ParseCustomFormat(ParsedMovieInfo movieInfo)
{ {
var matches = MatchFormatTags(movieInfo); var matches = MatchFormatTags(movieInfo);
@ -178,22 +155,6 @@ namespace NzbDrone.Core.Parser
return matches; return matches;
} }
public LocalMovie GetLocalMovie(string filename, ParsedMovieInfo minimalInfo, Movie movie, List<object> helpers, bool sceneSource = false)
{
var enhanced = EnhanceMinimalInfo(minimalInfo, helpers);
return new LocalMovie
{
Movie = movie,
Quality = enhanced.Quality,
Languages = enhanced.Languages,
Path = filename,
ParsedMovieInfo = enhanced,
ExistingFile = movie.Path.IsParentPath(filename),
MediaInfo = helpers.FirstOrDefault(h => h?.GetType() == typeof(MediaInfoModel)) as MediaInfoModel
};
}
public ParsedMovieInfo ParseMinimalMovieInfo(string file, bool isDir = false) public ParsedMovieInfo ParseMinimalMovieInfo(string file, bool isDir = false)
{ {
return Parser.ParseMovieTitle(file, _config.ParsingLeniency > 0, isDir); return Parser.ParseMovieTitle(file, _config.ParsingLeniency > 0, isDir);

@ -349,7 +349,7 @@ namespace NzbDrone.Core.Parser
result.Source = MediaFileExtensions.GetSourceForExtension(Path.GetExtension(name)); result.Source = MediaFileExtensions.GetSourceForExtension(Path.GetExtension(name));
result.Resolution = MediaFileExtensions.GetResolutionForExtension(Path.GetExtension(name)); result.Resolution = MediaFileExtensions.GetResolutionForExtension(Path.GetExtension(name));
result.QualitySource = QualitySource.Extension; result.QualityDetectionSource = QualityDetectionSource.Extension;
} }
catch (ArgumentException) catch (ArgumentException)
{ {

@ -0,0 +1,9 @@
namespace NzbDrone.Core.Qualities
{
public enum QualityDetectionSource
{
Name,
Extension,
MediaInfo
}
}

@ -0,0 +1,41 @@
using System.Linq;
using NLog;
using NzbDrone.Common.Instrumentation;
using NzbDrone.Core.CustomFormats;
namespace NzbDrone.Core.Qualities
{
public static class QualityFinder
{
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(QualityFinder));
public static Quality FindBySourceAndResolution(Source source, Resolution resolution)
{
var matchingQuality = Quality.All.SingleOrDefault(q => q.Source == source && q.Resolution == resolution);
if (matchingQuality != null)
{
return matchingQuality;
}
var matchingResolution = Quality.All.Where(q => q.Resolution == resolution)
.OrderBy(q => q.Source)
.ToList();
var nearestQuality = Quality.Unknown;
foreach (var quality in matchingResolution)
{
if (quality.Source >= source)
{
nearestQuality = quality;
break;
}
}
Logger.Warn("Unable to find exact quality for {0} and {1}. Using {2} as fallback", source, resolution, nearestQuality);
return nearestQuality;
}
}
}

@ -25,7 +25,7 @@ namespace NzbDrone.Core.Qualities
public string HardcodedSubs { get; set; } public string HardcodedSubs { get; set; }
[JsonIgnore] [JsonIgnore]
public QualitySource QualitySource { get; set; } public QualityDetectionSource QualityDetectionSource { get; set; }
public QualityModel() public QualityModel()
: this(Quality.Unknown, new Revision()) : this(Quality.Unknown, new Revision())

@ -2,8 +2,12 @@
{ {
public enum QualitySource public enum QualitySource
{ {
Name, Unknown,
Extension, Television,
MediaInfo TelevisionRaw,
Web,
WebRip,
DVD,
Bluray
} }
} }

@ -5,8 +5,8 @@ namespace NzbDrone.Core.Update.Commands
public class ApplicationUpdateCommand : Command public class ApplicationUpdateCommand : Command
{ {
public override bool SendUpdatesToClient => true; public override bool SendUpdatesToClient => true;
public override bool RequiresDiskAccess => true; public override bool IsExclusive => true;
public override string CompletionMessage => "Restarting Radarr to apply updates"; public override string CompletionMessage => null;
} }
} }

@ -91,7 +91,6 @@ namespace Radarr.Http
break; break;
case Lifetime.PerRequest: case Lifetime.PerRequest:
throw new InvalidOperationException("Unable to directly register a per request lifetime."); throw new InvalidOperationException("Unable to directly register a per request lifetime.");
break;
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }
@ -118,7 +117,6 @@ namespace Radarr.Http
break; break;
case Lifetime.PerRequest: case Lifetime.PerRequest:
throw new InvalidOperationException("Unable to directly register a per request lifetime."); throw new InvalidOperationException("Unable to directly register a per request lifetime.");
break;
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }

Loading…
Cancel
Save