diff --git a/src/NzbDrone.Api/Movies/MovieBulkImportModule.cs b/src/NzbDrone.Api/Movies/MovieBulkImportModule.cs index 977cdeba8..ea71e8a12 100644 --- a/src/NzbDrone.Api/Movies/MovieBulkImportModule.cs +++ b/src/NzbDrone.Api/Movies/MovieBulkImportModule.cs @@ -125,7 +125,7 @@ namespace NzbDrone.Api.Movies 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(); @@ -136,10 +136,10 @@ namespace NzbDrone.Api.Movies m.MovieFile = new MovieFile { Path = local.Path, - Edition = local.ParsedMovieInfo.Edition, + Edition = local.Edition, Quality = local.Quality, MediaInfo = local.MediaInfo, - ReleaseGroup = local.ParsedMovieInfo.ReleaseGroup, + ReleaseGroup = local.ReleaseGroup, RelativePath = f.Path.GetRelativePath(local.Path) }; } diff --git a/src/NzbDrone.Core.Test/MediaFiles/DiskScanServiceTests/ScanFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/DiskScanServiceTests/ScanFixture.cs index ec7c6ff03..eb93bfc29 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/DiskScanServiceTests/ScanFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/DiskScanServiceTests/ScanFixture.cs @@ -95,7 +95,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests Subject.Scan(_movie); Mocker.GetMock() - .Verify(v => v.GetImportDecisions(It.Is>(l => l.Count == 1), _movie, true), Times.Once()); + .Verify(v => v.GetImportDecisions(It.Is>(l => l.Count == 1), _movie), Times.Once()); } [Test] @@ -113,7 +113,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests Subject.Scan(_movie); Mocker.GetMock() - .Verify(v => v.GetImportDecisions(It.Is>(l => l.Count == 1), _movie, true), Times.Once()); + .Verify(v => v.GetImportDecisions(It.Is>(l => l.Count == 1), _movie), Times.Once()); } [Test] @@ -135,7 +135,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests Subject.Scan(_movie); Mocker.GetMock() - .Verify(v => v.GetImportDecisions(It.Is>(l => l.Count == 4), _movie, true), Times.Once()); + .Verify(v => v.GetImportDecisions(It.Is>(l => l.Count == 4), _movie), Times.Once()); } [Test] @@ -154,7 +154,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests Subject.Scan(_movie); Mocker.GetMock() - .Verify(v => v.GetImportDecisions(It.Is>(l => l.Count == 1), _movie, true), Times.Once()); + .Verify(v => v.GetImportDecisions(It.Is>(l => l.Count == 1), _movie), Times.Once()); } [Test] @@ -174,7 +174,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests Subject.Scan(_movie); Mocker.GetMock() - .Verify(v => v.GetImportDecisions(It.Is>(l => l.Count == 1), _movie, true), Times.Once()); + .Verify(v => v.GetImportDecisions(It.Is>(l => l.Count == 1), _movie), Times.Once()); } [Test] @@ -191,7 +191,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests Subject.Scan(_movie); Mocker.GetMock() - .Verify(v => v.GetImportDecisions(It.Is>(l => l.Count == 1), _movie, true), Times.Once()); + .Verify(v => v.GetImportDecisions(It.Is>(l => l.Count == 1), _movie), Times.Once()); } [Test] @@ -208,7 +208,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests Subject.Scan(_movie); Mocker.GetMock() - .Verify(v => v.GetImportDecisions(It.Is>(l => l.Count == 1), _movie, true), Times.Once()); + .Verify(v => v.GetImportDecisions(It.Is>(l => l.Count == 1), _movie), Times.Once()); } [Test] @@ -226,7 +226,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests Subject.Scan(_movie); Mocker.GetMock() - .Verify(v => v.GetImportDecisions(It.Is>(l => l.Count == 2), _movie, true), Times.Once()); + .Verify(v => v.GetImportDecisions(It.Is>(l => l.Count == 2), _movie), Times.Once()); } [Test] @@ -243,7 +243,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests Subject.Scan(_movie); Mocker.GetMock() - .Verify(v => v.GetImportDecisions(It.Is>(l => l.Count == 2), _movie, true), Times.Once()); + .Verify(v => v.GetImportDecisions(It.Is>(l => l.Count == 2), _movie), Times.Once()); } [Test] @@ -260,7 +260,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests Subject.Scan(_movie); Mocker.GetMock() - .Verify(v => v.GetImportDecisions(It.Is>(l => l.Count == 1), _movie, true), Times.Once()); + .Verify(v => v.GetImportDecisions(It.Is>(l => l.Count == 1), _movie), Times.Once()); } } } diff --git a/src/NzbDrone.Core.Test/MediaFiles/DownloadedMoviesImportServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/DownloadedMoviesImportServiceFixture.cs index 01652b721..672ea5ecd 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/DownloadedMoviesImportServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/DownloadedMoviesImportServiceFixture.cs @@ -172,11 +172,9 @@ namespace NzbDrone.Core.Test.MediaFiles Mocker.GetMock() .Setup(s => s.IsSample(It.IsAny(), - It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())) - .Returns(true); + .Returns(DetectSampleResult.Sample); Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory)); @@ -244,11 +242,9 @@ namespace NzbDrone.Core.Test.MediaFiles Mocker.GetMock() .Setup(s => s.IsSample(It.IsAny(), - It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())) - .Returns(true); + .Returns(DetectSampleResult.Sample); Mocker.GetMock() .Setup(s => s.GetFiles(It.IsAny(), SearchOption.AllDirectories)) @@ -289,7 +285,7 @@ namespace NzbDrone.Core.Test.MediaFiles Subject.ProcessPath(fileName); Mocker.GetMock() - .Verify(s => s.GetImportDecisions(It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), true, false), Times.Once()); + .Verify(s => s.GetImportDecisions(It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), true), Times.Once()); } [Test] @@ -313,7 +309,7 @@ namespace NzbDrone.Core.Test.MediaFiles var result = Subject.ProcessPath(fileName); Mocker.GetMock() - .Verify(s => s.GetImportDecisions(It.IsAny>(), It.IsAny(), It.IsAny(), null, true, false), Times.Once()); + .Verify(s => s.GetImportDecisions(It.IsAny>(), It.IsAny(), It.IsAny(), null, true), Times.Once()); } [Test] @@ -355,11 +351,9 @@ namespace NzbDrone.Core.Test.MediaFiles Mocker.GetMock() .Setup(s => s.IsSample(It.IsAny(), - It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())) - .Returns(true); + .Returns(DetectSampleResult.Sample); Mocker.GetMock() .Setup(s => s.GetFileSize(It.IsAny())) diff --git a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedMoviesFixture.cs similarity index 96% rename from src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs rename to src/NzbDrone.Core.Test/MediaFiles/ImportApprovedMoviesFixture.cs index 9ebc253e1..b1367f334 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedMoviesFixture.cs @@ -22,7 +22,7 @@ namespace NzbDrone.Core.Test.MediaFiles { [TestFixture] //TODO: Update all of this for movies. - public class ImportApprovedEpisodesFixture : CoreTest + public class ImportApprovedMoviesFixture : CoreTest { private List _rejectedDecisions; private List _approvedDecisions; @@ -51,10 +51,7 @@ namespace NzbDrone.Core.Test.MediaFiles Movie = movie, Path = Path.Combine(movie.Path, "30 Rock - S01E01 - Pilot.avi"), Quality = new QualityModel(), - ParsedMovieInfo = new ParsedMovieInfo() - { - ReleaseGroup = "DRONE" - } + ReleaseGroup = "DRONE" })); diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeFileMovingServiceTests/MoveEpisodeFileFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MovieFileMovingServiceTests/MoveMovieFileFixture.cs similarity index 72% rename from src/NzbDrone.Core.Test/MediaFiles/EpisodeFileMovingServiceTests/MoveEpisodeFileFixture.cs rename to src/NzbDrone.Core.Test/MediaFiles/MovieFileMovingServiceTests/MoveMovieFileFixture.cs index 187270e4c..7c9851179 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/EpisodeFileMovingServiceTests/MoveEpisodeFileFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/MovieFileMovingServiceTests/MoveMovieFileFixture.cs @@ -15,29 +15,29 @@ using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Movies; using NzbDrone.Test.Common; -namespace NzbDrone.Core.Test.MediaFiles.EpisodeFileMovingServiceTests +namespace NzbDrone.Core.Test.MediaFiles.MovieFileMovingServiceTests { [TestFixture] - public class MoveEpisodeFileFixture : CoreTest + public class MoveMovieFileFixture : CoreTest { - private Movie _series; - private MovieFile _episodeFile; - private LocalMovie _localEpisode; + private Movie _movie; + private MovieFile _movieFile; + private LocalMovie _localMovie; [SetUp] public void Setup() { - _series = Builder.CreateNew() - .With(s => s.Path = @"C:\Test\TV\Series".AsOsAgnostic()) + _movie = Builder.CreateNew() + .With(s => s.Path = @"C:\Test\Movies\Movie".AsOsAgnostic()) .Build(); - _episodeFile = Builder.CreateNew() + _movieFile = Builder.CreateNew() .With(f => f.Path = null) - .With(f => f.RelativePath = @"Season 1\File.avi") + .With(f => f.RelativePath = @"File.avi") .Build(); - _localEpisode = Builder.CreateNew() - .With(l => l.Movie = _series) + _localMovie = Builder.CreateNew() + .With(l => l.Movie = _movie) .Build(); Mocker.GetMock() @@ -46,9 +46,9 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeFileMovingServiceTests Mocker.GetMock() .Setup(s => s.BuildFilePath(It.IsAny(), It.IsAny(), It.IsAny())) - .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() .Setup(s => s.FolderExists(rootFolder)) .Returns(true); @@ -67,7 +67,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeFileMovingServiceTests .Setup(s => s.InheritFolderPermissions(It.IsAny())) .Throws(); - Subject.MoveMovieFile(_episodeFile, _localEpisode); + Subject.MoveMovieFile(_movieFile, _localMovie); } [Test] @@ -79,13 +79,13 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeFileMovingServiceTests .Setup(s => s.InheritFolderPermissions(It.IsAny())) .Throws(); - Subject.MoveMovieFile(_episodeFile, _localEpisode); + Subject.MoveMovieFile(_movieFile, _localMovie); } [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() .Verify(s => s.PublishEvent(It.Is(p => @@ -93,13 +93,13 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeFileMovingServiceTests } [Test] - public void should_not_notify_if_series_folder_already_exists() + public void should_not_notify_if_movie_folder_already_exists() { Mocker.GetMock() - .Setup(s => s.FolderExists(_series.Path)) + .Setup(s => s.FolderExists(_movie.Path)) .Returns(true); - Subject.MoveMovieFile(_episodeFile, _localEpisode); + Subject.MoveMovieFile(_movieFile, _localMovie); Mocker.GetMock() .Verify(s => s.PublishEvent(It.Is(p => diff --git a/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateLanguageFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateLanguageFixture.cs new file mode 100644 index 000000000..a8d7ca89c --- /dev/null +++ b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateLanguageFixture.cs @@ -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 + { + private LocalMovie _localMovie; + + [SetUp] + public void Setup() + { + _localMovie = Builder.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 } + }; + } + + [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); + } + } +} diff --git a/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateQualityFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateQualityFixture.cs new file mode 100644 index 000000000..2a5ede3c8 --- /dev/null +++ b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateQualityFixture.cs @@ -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 + { + private Mock _mediaInfoAugmenter; + private Mock _fileExtensionAugmenter; + private Mock _nameAugmenter; + + private IEnumerable _qualityAugmenters; + + [SetUp] + public void Setup() + { + _mediaInfoAugmenter = new Mock(); + _fileExtensionAugmenter = new Mock(); + _nameAugmenter = new Mock(); + + _mediaInfoAugmenter.Setup(s => s.AugmentQuality(It.IsAny())) + .Returns(AugmentQualityResult.ResolutionOnly(Resolution.R1080P, Confidence.MediaInfo)); + + _fileExtensionAugmenter.Setup(s => s.AugmentQuality(It.IsAny())) + .Returns(new AugmentQualityResult(Source.TV, Confidence.Fallback, Resolution.R720P, Confidence.Fallback, new Revision())); + + _nameAugmenter.Setup(s => s.AugmentQuality(It.IsAny())) + .Returns(new AugmentQualityResult(Source.TV, Confidence.Default, Resolution.R480P, Confidence.Default, new Revision())); + } + + private void GivenAugmenters(params Mock[] mocks) + { + Mocker.SetConstant>(mocks.Select(c => c.Object)); + } + + [Test] + public void should_return_HDTV720_from_extension_when_other_augments_are_null() + { + var nullMock = new Mock(); + nullMock.Setup(s => s.AugmentQuality(It.IsAny())) + .Returns(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); + } + } +} diff --git a/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromMediaInfoFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromMediaInfoFixture.cs new file mode 100644 index 000000000..fe8c647dc --- /dev/null +++ b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromMediaInfoFixture.cs @@ -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 + { + [Test] + public void should_return_null_if_media_info_is_null() + { + var localMovie = Builder.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.CreateNew() + .With(m => m.Width = 0) + .Build(); + + var localMovie = Builder.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.CreateNew() + .With(m => m.Width = mediaInfoWidth) + .Build(); + + var localMovie = Builder.CreateNew() + .With(l => l.MediaInfo = mediaInfo) + .Build(); + + var result = Subject.AugmentQuality(localMovie); + + result.Should().NotBe(null); + result.Resolution.Should().Be(expectedResolution); + } + } +} diff --git a/src/NzbDrone.Core.Test/MediaFiles/MovieImport/SampleServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/DetectSampleFixture.cs similarity index 61% rename from src/NzbDrone.Core.Test/MediaFiles/MovieImport/SampleServiceFixture.cs rename to src/NzbDrone.Core.Test/MediaFiles/MovieImport/DetectSampleFixture.cs index 46f14a86f..9fc22c432 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/MovieImport/SampleServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/DetectSampleFixture.cs @@ -10,11 +10,12 @@ using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Movies; +using NzbDrone.Test.Common; namespace NzbDrone.Core.Test.MediaFiles.MovieImport { [TestFixture] - public class SampleServiceFixture : CoreTest + public class DetectSampleFixture : CoreTest { private Movie _movie; private LocalMovie _localMovie; @@ -26,17 +27,13 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport .With(s => s.Runtime = 30) .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.Size = size; + _localMovie = new LocalMovie + { + Path = @"C:\Test\30 Rock\30.rock.s01e01.avi", + Movie = _movie, + Quality = new QualityModel(Quality.HDTV720p) + }; } private void GivenRuntime(int seconds) @@ -51,7 +48,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport { _localMovie.Path = @"C:\Test\some.show.s01e01.flv"; - ShouldBeFalse(); + ShouldBeNotSample(); Mocker.GetMock().Verify(c => c.GetRunTime(It.IsAny()), Times.Never()); } @@ -61,7 +58,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport { _localMovie.Path = @"C:\Test\some.show.s01e01.strm"; - ShouldBeFalse(); + ShouldBeNotSample(); Mocker.GetMock().Verify(c => c.GetRunTime(It.IsAny()), Times.Never()); } @@ -70,12 +67,9 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport public void should_use_runtime() { GivenRuntime(120); - GivenFileSize(1000.Megabytes()); Subject.IsSample(_localMovie.Movie, - _localMovie.Quality, _localMovie.Path, - _localMovie.Size, false); Mocker.GetMock().Verify(v => v.GetRunTime(It.IsAny()), Times.Once()); @@ -86,7 +80,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport { GivenRuntime(60); - ShouldBeTrue(); + ShouldBeSample(); } [Test] @@ -94,7 +88,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport { GivenRuntime(600); - ShouldBeFalse(); + ShouldBeNotSample(); } [Test] @@ -103,51 +97,53 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport _movie.Runtime = 6; GivenRuntime(299); - ShouldBeFalse(); + ShouldBeNotSample(); } [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() - .Setup(s => s.GetRunTime(It.IsAny())) - .Throws(); + _movie.Runtime = 2; + GivenRuntime(60); - GivenFileSize(1000.Megabytes()); - ShouldBeFalse(); + ShouldBeNotSample(); } [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() - .Setup(s => s.GetRunTime(It.IsAny())) - .Throws(); + _movie.Runtime = 2; + GivenRuntime(10); - GivenFileSize(1.Megabytes()); - ShouldBeTrue(); + ShouldBeSample(); } + [Test] + public void should_return_indeterminate_if_mediainfo_result_is_null() + { + Mocker.GetMock() + .Setup(s => s.GetRunTime(It.IsAny())) + .Returns((TimeSpan?)null); + Subject.IsSample(_localMovie.Movie, + _localMovie.Path, + false).Should().Be(DetectSampleResult.Indeterminate); + ExceptionVerification.ExpectedErrors(1); + } - - private void ShouldBeTrue() + private void ShouldBeSample() { Subject.IsSample(_localMovie.Movie, - _localMovie.Quality, - _localMovie.Path, - _localMovie.Size, - false).Should().BeTrue(); + _localMovie.Path, + false).Should().Be(DetectSampleResult.Sample); } - private void ShouldBeFalse() + private void ShouldBeNotSample() { Subject.IsSample(_localMovie.Movie, - _localMovie.Quality, _localMovie.Path, - _localMovie.Size, - false).Should().BeFalse(); + false).Should().Be(DetectSampleResult.NotSample); } } } diff --git a/src/NzbDrone.Core.Test/MediaFiles/MovieImport/ImportDecisionMakerFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/ImportDecisionMakerFixture.cs index b94ed7ea4..15fc9093b 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/MovieImport/ImportDecisionMakerFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/ImportDecisionMakerFixture.cs @@ -15,6 +15,7 @@ using NzbDrone.Core.Movies; using NzbDrone.Test.Common; using FizzWare.NBuilder; using NzbDrone.Core.Download; +using NzbDrone.Core.MediaFiles.MovieImport.Aggregation; namespace NzbDrone.Core.Test.MediaFiles.MovieImport { @@ -75,10 +76,6 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport Quality = _quality }; - Mocker.GetMock() - .Setup(c => c.GetLocalMovie(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) - .Returns(_localMovie); - Mocker.GetMock() .Setup(c => c.ParseMinimalPathMovieInfo(It.IsAny())) .Returns(_fileInfo); @@ -100,6 +97,16 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport .Returns(_videoFiles); } + private void GivenAugmentationSuccess() + { + Mocker.GetMock() + .Setup(s => s.Augment(It.IsAny(), It.IsAny())) + .Callback((localMovie, otherFiles) => + { + localMovie.Movie = _localMovie.Movie; + }); + } + [Test] public void should_call_all_specifications() { @@ -108,12 +115,12 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport Subject.GetImportDecisions(_videoFiles, new Movie(), downloadClientItem, null, false); - _fail1.Verify(c => c.IsSatisfiedBy(_localMovie, downloadClientItem), Times.Once()); - _fail2.Verify(c => c.IsSatisfiedBy(_localMovie, downloadClientItem), Times.Once()); - _fail3.Verify(c => c.IsSatisfiedBy(_localMovie, downloadClientItem), Times.Once()); - _pass1.Verify(c => c.IsSatisfiedBy(_localMovie, downloadClientItem), Times.Once()); - _pass2.Verify(c => c.IsSatisfiedBy(_localMovie, downloadClientItem), Times.Once()); - _pass3.Verify(c => c.IsSatisfiedBy(_localMovie, downloadClientItem), Times.Once()); + _fail1.Verify(c => c.IsSatisfiedBy(It.IsAny(), downloadClientItem), Times.Once()); + _fail2.Verify(c => c.IsSatisfiedBy(It.IsAny(), downloadClientItem), Times.Once()); + _fail3.Verify(c => c.IsSatisfiedBy(It.IsAny(), downloadClientItem), Times.Once()); + _pass1.Verify(c => c.IsSatisfiedBy(It.IsAny(), downloadClientItem), Times.Once()); + _pass2.Verify(c => c.IsSatisfiedBy(It.IsAny(), downloadClientItem), Times.Once()); + _pass3.Verify(c => c.IsSatisfiedBy(It.IsAny(), downloadClientItem), Times.Once()); } [Test] @@ -160,8 +167,8 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport { GivenSpecifications(_pass1); - Mocker.GetMock() - .Setup(c => c.GetLocalMovie(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) + Mocker.GetMock() + .Setup(c => c.Augment(It.IsAny(), It.IsAny())) .Throws(); _videoFiles = new List @@ -175,8 +182,8 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport Subject.GetImportDecisions(_videoFiles, _movie); - Mocker.GetMock() - .Verify(c => c.GetLocalMovie(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny()), Times.Exactly(_videoFiles.Count)); + Mocker.GetMock() + .Verify(c => c.Augment(It.IsAny(), It.IsAny()), Times.Exactly(_videoFiles.Count)); ExceptionVerification.ExpectedErrors(3); } @@ -196,88 +203,16 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport var fileNames = _videoFiles.Select(System.IO.Path.GetFileName); - Mocker.GetMock() - .Verify( - c => c.GetLocalMovie(It.IsAny(), - It.Is(p => fileNames.Contains(p.SimpleReleaseTitle)), It.IsAny(), - It.IsAny>(), It.IsAny()), 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); + Mocker.GetMock() + .Setup(c => c.Augment(It.IsAny(), It.IsAny())) + .Throws(); } [Test] public void should_return_a_decision_when_exception_is_caught() { - Mocker.GetMock() - .Setup(c => c.GetLocalMovie(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) + Mocker.GetMock() + .Setup(c => c.Augment(It.IsAny(), It.IsAny())) .Throws(); _videoFiles = new List diff --git a/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Specifications/MatchesFolderSpecificationFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Specifications/MatchesFolderSpecificationFixture.cs index 8490b185f..5f6d16b75 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Specifications/MatchesFolderSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Specifications/MatchesFolderSpecificationFixture.cs @@ -18,7 +18,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Specifications { _localMovie = Builder.CreateNew() .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.CreateNew() .Build()) .Build(); diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index dc1eb89c8..6afbeec05 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -291,10 +291,13 @@ - + + + + - + @@ -302,7 +305,7 @@ - + diff --git a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/AugmentersTests/AugmentWithMediaInfoFixture.cs b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/AugmentersTests/AugmentWithMediaInfoFixture.cs index fa323597c..6b25187ab 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/AugmentersTests/AugmentWithMediaInfoFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/AugmentersTests/AugmentWithMediaInfoFixture.cs @@ -43,7 +43,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests.AugmentersTests var movieInfo = Subject.AugmentMovieInfo(MovieInfo, mediaInfo); 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)] @@ -83,7 +83,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests.AugmentersTests var movieInfo = Subject.AugmentMovieInfo(MovieInfo, mediaInfo); movieInfo.Quality.Resolution.ShouldBeEquivalentTo(resolution); - movieInfo.Quality.QualitySource.ShouldBeEquivalentTo(QualitySource.Name); + movieInfo.Quality.QualityDetectionSource.ShouldBeEquivalentTo(QualityDetectionSource.Name); } } } diff --git a/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs index ad8fc45ec..6b85a6738 100644 --- a/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs @@ -331,7 +331,7 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("White.Van.Man.2011.S02E01.WS.PDTV.x264-REPACK-TLA")] 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")] @@ -341,7 +341,7 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("[CR] Sailor Moon - 004 [48CE2D0F].avi")] 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")] diff --git a/src/NzbDrone.Core/MediaFiles/DiskScanService.cs b/src/NzbDrone.Core/MediaFiles/DiskScanService.cs index 4ae7a5481..f1d6ac2e5 100644 --- a/src/NzbDrone.Core/MediaFiles/DiskScanService.cs +++ b/src/NzbDrone.Core/MediaFiles/DiskScanService.cs @@ -119,7 +119,7 @@ namespace NzbDrone.Core.MediaFiles CleanMediaFiles(movie, mediaFileList); var decisionsStopwatch = Stopwatch.StartNew(); - var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, movie, true); + var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, movie); decisionsStopwatch.Stop(); _logger.Trace("Import decisions complete for: {0} [{1}]", movie, decisionsStopwatch.Elapsed); _importApprovedMovies.Import(decisions, false); diff --git a/src/NzbDrone.Core/MediaFiles/DownloadedMovieImportService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedMovieImportService.cs index 54a72254f..87b1d0d84 100644 --- a/src/NzbDrone.Core/MediaFiles/DownloadedMovieImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/DownloadedMovieImportService.cs @@ -130,7 +130,7 @@ namespace NzbDrone.Core.MediaFiles 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); 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); if ((downloadClientItem == null || downloadClientItem.CanBeRemoved) && @@ -252,7 +252,7 @@ namespace NzbDrone.Core.MediaFiles } } - var decisions = _importDecisionMaker.GetImportDecisions(new List() { fileInfo.FullName }, movie, downloadClientItem, null, true, false); + var decisions = _importDecisionMaker.GetImportDecisions(new List() { fileInfo.FullName }, movie, downloadClientItem, null, true); return _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode); } diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/AggregationFailedException.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/AggregationFailedException.cs new file mode 100644 index 000000000..8269cf9e2 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/AggregationFailedException.cs @@ -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) + { + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/AggregationService.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/AggregationService.cs new file mode 100644 index 000000000..715f522bc --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/AggregationService.cs @@ -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 _augmenters; + private readonly IDiskProvider _diskProvider; + private readonly IVideoFileInfoReader _videoFileInfoReader; + private readonly IConfigService _configService; + private readonly Logger _logger; + + public AggregationService(IEnumerable 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; + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateLanguage.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateLanguage.cs new file mode 100644 index 000000000..48601cb1c --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateLanguage.cs @@ -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(); + + languages.AddRange(GetLanguage(localMovie.DownloadClientMovieInfo)); + languages.AddRange(GetLanguage(localMovie.FolderMovieInfo)); + languages.AddRange(GetLanguage(localMovie.FileMovieInfo)); + + var language = new List { languages.FirstOrDefault(l => l != Language.English) ?? Language.English }; + + _logger.Debug("Using language: {0}", language.First()); + + localMovie.Languages = language; + + return localMovie; + } + + private List GetLanguage(ParsedMovieInfo parsedMovieInfo) + { + if (parsedMovieInfo == null) + { + // English is the default language when otherwise unknown + + return new List { Language.English }; + } + + return parsedMovieInfo.Languages; + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateQuality.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateQuality.cs new file mode 100644 index 000000000..b5c03b174 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateQuality.cs @@ -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 _augmentQualities; + private readonly Logger _logger; + + public AggregateQuality(IEnumerable 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; + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateReleaseGroup.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateReleaseGroup.cs new file mode 100644 index 000000000..a91ccf23b --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateReleaseGroup.cs @@ -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; + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromDownloadClientItem.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromDownloadClientItem.cs new file mode 100644 index 000000000..a5586b28d --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromDownloadClientItem.cs @@ -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); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromFileName.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromFileName.cs new file mode 100644 index 000000000..5d8a5cdcc --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromFileName.cs @@ -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); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromFolder.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromFolder.cs new file mode 100644 index 000000000..2c4f40fc7 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromFolder.cs @@ -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); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromMediaInfo.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromMediaInfo.cs new file mode 100644 index 000000000..9f7fdc401 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromMediaInfo.cs @@ -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; + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityResult.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityResult.cs new file mode 100644 index 000000000..bc3e25ef4 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityResult.cs @@ -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); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/Confidence.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/Confidence.cs new file mode 100644 index 000000000..5da614206 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/Confidence.cs @@ -0,0 +1,10 @@ +namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenters.Quality +{ + public enum Confidence + { + Fallback, + Default, + Tag, + MediaInfo + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/IAugmentQuality.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/IAugmentQuality.cs new file mode 100644 index 000000000..14b2648fe --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/IAugmentQuality.cs @@ -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); + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/IAggregateLocalMovie.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/IAggregateLocalMovie.cs new file mode 100644 index 000000000..ada6fdc14 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/IAggregateLocalMovie.cs @@ -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); + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/DetectSample.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/DetectSample.cs index 94f5db07d..8dc4f7c11 100644 --- a/src/NzbDrone.Core/MediaFiles/MovieImport/DetectSample.cs +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/DetectSample.cs @@ -1,17 +1,14 @@ using System; -using System.Collections.Generic; using System.IO; using NLog; -using NzbDrone.Core.CustomFormats; using NzbDrone.Core.MediaFiles.MediaInfo; -using NzbDrone.Core.Qualities; using NzbDrone.Core.Movies; namespace NzbDrone.Core.MediaFiles.MovieImport { 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 @@ -19,23 +16,18 @@ namespace NzbDrone.Core.MediaFiles.MovieImport private readonly IVideoFileInfoReader _videoFileInfoReader; private readonly Logger _logger; - //private static List _largeSampleSizeQualities = new List { Quality.HDTV1080p, Quality.WEBDL1080p, Quality.Bluray1080p }; - private static List _largeSampleSizeResolutions = new List{Resolution.R1080P, Resolution.R2160P}; - public DetectSample(IVideoFileInfoReader videoFileInfoReader, Logger logger) { _videoFileInfoReader = videoFileInfoReader; _logger = logger; } - public static long SampleSizeLimit => 70.Megabytes(); - - public bool IsSample(Movie movie, QualityModel quality, string path, long size, bool isSpecial) + public DetectSampleResult IsSample(Movie movie, string path, bool isSpecial) { if (isSpecial) { _logger.Debug("Special, skipping sample check"); - return false; + return DetectSampleResult.NotSample; } var extension = Path.GetExtension(path); @@ -43,72 +35,64 @@ namespace NzbDrone.Core.MediaFiles.MovieImport if (extension != null && extension.Equals(".flv", StringComparison.InvariantCultureIgnoreCase)) { _logger.Debug("Skipping sample check for .flv file"); - return false; + return DetectSampleResult.NotSample; } if (extension != null && extension.Equals(".strm", StringComparison.InvariantCultureIgnoreCase)) { _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); - var minimumRuntime = GetMinimumAllowedRuntime(movie); - - 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; - } + _logger.Error("Failed to get runtime from the file, make sure mediainfo is available"); + return DetectSampleResult.Indeterminate; } - 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"); - 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) - { - _logger.Debug("1080p file is less than sample limit"); - return true; - } + return 15; } - if (size < SampleSizeLimit) + //Webisodes - 90 seconds + if (movie.Runtime <= 10) { - _logger.Debug("File is less than sample limit"); - return true; + return 90; } - return false; - } - - private int GetMinimumAllowedRuntime(Movie movie) - { - if (movie.Runtime < 1) + //30 minute episodes - 5 minutes + if (movie.Runtime <= 30) { - return 5 * 60; + return 300; } - return movie.Runtime / 5 * 60; + //60 minute episodes - 10 minutes + return 600; } } } diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/DetectSampleResult.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/DetectSampleResult.cs new file mode 100644 index 000000000..d43158c1b --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/DetectSampleResult.cs @@ -0,0 +1,9 @@ +namespace NzbDrone.Core.MediaFiles.MovieImport +{ + public enum DetectSampleResult + { + Indeterminate, + Sample, + NotSample + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/ImportApprovedMovie.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/ImportApprovedMovie.cs index e59266d46..d5d839fd9 100644 --- a/src/NzbDrone.Core/MediaFiles/MovieImport/ImportApprovedMovie.cs +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/ImportApprovedMovie.cs @@ -85,8 +85,8 @@ namespace NzbDrone.Core.MediaFiles.MovieImport movieFile.Languages = localMovie.Languages; movieFile.MediaInfo = localMovie.MediaInfo; movieFile.Movie = localMovie.Movie; - movieFile.ReleaseGroup = localMovie.ParsedMovieInfo?.ReleaseGroup; - movieFile.Edition = localMovie.ParsedMovieInfo?.Edition; + movieFile.ReleaseGroup = localMovie.ReleaseGroup; + movieFile.Edition = localMovie.Edition; bool copyOnly; switch (importMode) diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/ImportDecisionMaker.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/ImportDecisionMaker.cs index 610e61f7b..563686dbd 100644 --- a/src/NzbDrone.Core/MediaFiles/MovieImport/ImportDecisionMaker.cs +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/ImportDecisionMaker.cs @@ -15,6 +15,7 @@ using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; using NzbDrone.Core.Movies; using NzbDrone.Core.MediaFiles.MediaInfo; +using NzbDrone.Core.MediaFiles.MovieImport.Aggregation; namespace NzbDrone.Core.MediaFiles.MovieImport @@ -22,18 +23,15 @@ namespace NzbDrone.Core.MediaFiles.MovieImport public interface IMakeImportDecision { List GetImportDecisions(List videoFiles, Movie movie); - List GetImportDecisions(List videoFiles, Movie movie, bool shouldCheckQuality); - List GetImportDecisions(List videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource, bool shouldCheckQuality); List GetImportDecisions(List videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource); } public class ImportDecisionMaker : IMakeImportDecision { private readonly IEnumerable _specifications; - private readonly IParsingService _parsingService; private readonly IMediaFileService _mediaFileService; + private readonly IAggregationService _aggregationService; private readonly IDiskProvider _diskProvider; - private readonly IVideoFileInfoReader _videoFileInfoReader; private readonly IDetectSample _detectSample; private readonly IQualityDefinitionService _qualitiesService; private readonly IConfigService _config; @@ -42,10 +40,9 @@ namespace NzbDrone.Core.MediaFiles.MovieImport private readonly Logger _logger; public ImportDecisionMaker(IEnumerable specifications, - IParsingService parsingService, IMediaFileService mediaFileService, + IAggregationService aggregationService, IDiskProvider diskProvider, - IVideoFileInfoReader videoFileInfoReader, IDetectSample detectSample, IQualityDefinitionService qualitiesService, IConfigService config, @@ -54,10 +51,9 @@ namespace NzbDrone.Core.MediaFiles.MovieImport Logger logger) { _specifications = specifications; - _parsingService = parsingService; _mediaFileService = mediaFileService; + _aggregationService = aggregationService; _diskProvider = diskProvider; - _videoFileInfoReader = videoFileInfoReader; _detectSample = detectSample; _qualitiesService = qualitiesService; _config = config; @@ -68,12 +64,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport public List GetImportDecisions(List videoFiles, Movie movie) { - return GetImportDecisions(videoFiles, movie, null, null, true, false); - } - - public List GetImportDecisions(List videoFiles, Movie movie, bool shouldCheckQuality = false) - { - return GetImportDecisions(videoFiles, movie, null, null, true, shouldCheckQuality); + return GetImportDecisions(videoFiles, movie, null, null, true); } public List GetImportDecisions(List 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()); - var shouldUseFolderName = ShouldUseFolderName(videoFiles, movie, folderInfo); - var decisions = new List(); + ParsedMovieInfo downloadClientItemInfo = null; - 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; - } - - public List GetImportDecisions(List 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 nonSampleVideoFileCount = GetNonSampleVideoFileCount(newFiles, movie, downloadClientItemInfo, folderInfo); - var shouldUseFolderName = ShouldUseFolderName(videoFiles, movie, folderInfo); var decisions = new List(); 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; } - 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; - try - { - 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 fileMovieInfo = Parser.Parser.ParseMoviePath(localMovie.Path, false); - 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 - 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{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); + decision = new ImportDecision(localMovie, new Rejection("Invalid movie")); } else { - localMovie = new LocalMovie(); - 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")); + decision = GetDecision(localMovie, downloadClientItem); } } - 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")); } - //LocalMovie nullMovie = null; - - //decision = new ImportDecision(nullMovie, new Rejection("IMPLEMENTATION MISSING!!!")); + if (decision == null) + { + _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; } @@ -217,66 +185,19 @@ namespace NzbDrone.Core.MediaFiles.MovieImport return null; } - //TODO: Remove this method, since it is no longer needed. - private bool ShouldUseFolderName(List 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) + private int GetNonSampleVideoFileCount(List videoFiles, Movie movie, ParsedMovieInfo downloadClientItemInfo, ParsedMovieInfo folderInfo) { - if (folderInfo == null) - { - return false; - } - - if (folderInfo.Quality.Quality == Quality.Unknown) + return videoFiles.Count(file => { - return false; - } - - if (fileQuality.QualitySource == QualitySource.Extension) - { - return true; - } + var sample = _detectSample.IsSample(movie, file, false); - if (fileQuality.QualitySource == QualitySource.MediaInfo) - { - return false; - } + if (sample == DetectSampleResult.Sample) + { + return false; + } - if (new QualityModelComparer(movie.Profile).Compare(folderInfo.Quality, fileQuality) > 0) - { return true; - } - - return false; + }); } - - private bool ShouldCheckQualityForParsedQuality(Quality quality) - { - List shouldNotCheck = new List { 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; - } } } diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Manual/ManualImportService.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Manual/ManualImportService.cs index d5ff026f2..52e78ca39 100644 --- a/src/NzbDrone.Core/MediaFiles/MovieImport/Manual/ManualImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Manual/ManualImportService.cs @@ -17,6 +17,7 @@ using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Movies; +using NzbDrone.Core.MediaFiles.MovieImport.Aggregation; namespace NzbDrone.Core.MediaFiles.MovieImport.Manual { @@ -32,8 +33,8 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual private readonly IDiskScanService _diskScanService; private readonly IMakeImportDecision _importDecisionMaker; private readonly IMovieService _movieService; - private readonly IVideoFileInfoReader _videoFileInfoReader; private readonly IImportApprovedMovie _importApprovedMovie; + private readonly IAggregationService _aggregationService; private readonly ITrackedDownloadService _trackedDownloadService; private readonly IDownloadedMovieImportService _downloadedMovieImportService; private readonly IEventAggregator _eventAggregator; @@ -46,7 +47,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual IDiskScanService diskScanService, IMakeImportDecision importDecisionMaker, IMovieService movieService, - IVideoFileInfoReader videoFileInfoReader, + IAggregationService aggregationService, IImportApprovedMovie importApprovedMovie, ITrackedDownloadService trackedDownloadService, IDownloadedMovieImportService downloadedMovieImportService, @@ -60,7 +61,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual _diskScanService = diskScanService; _importDecisionMaker = importDecisionMaker; _movieService = movieService; - _videoFileInfoReader = videoFileInfoReader; + _aggregationService = aggregationService; _importApprovedMovie = importApprovedMovie; _trackedDownloadService = trackedDownloadService; _downloadedMovieImportService = downloadedMovieImportService; @@ -91,16 +92,17 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual return new List(); } - return new List { ProcessFile(path, downloadId) }; + var rootFolder = Path.GetDirectoryName(path); + return new List { ProcessFile(rootFolder, rootFolder, path, downloadId) }; } - return ProcessFolder(path, downloadId, filterExistingFiles); + return ProcessFolder(path, path, downloadId, filterExistingFiles); } - private List ProcessFolder(string folder, string downloadId, bool filterExistingFiles) + private List ProcessFolder(string rootFolder, string baseFolder, string downloadId, bool filterExistingFiles) { DownloadClientItem downloadClientItem = null; - var directoryInfo = new DirectoryInfo(folder); + var directoryInfo = new DirectoryInfo(baseFolder); var movie = _parsingService.GetMovie(directoryInfo.Name); 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) { - 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 firstHistoryItem = historyItems.OrderByDescending(h => h.Date).FirstOrDefault(); - var folderInfo = _parsingService.ParseMovieInfo(directoryInfo.Name, new List{firstHistoryItem}); - var movieFiles = _diskScanService.GetVideoFiles(folder).ToList(); - var decisions = _importDecisionMaker.GetImportDecisions(movieFiles, movie, downloadClientItem, folderInfo, SceneSource(movie, folder), false); + var folderInfo = Parser.Parser.ParseMovieTitle(directoryInfo.Name, false); + var movieFiles = _diskScanService.GetVideoFiles(baseFolder).ToList(); + var decisions = _importDecisionMaker.GetImportDecisions(movieFiles, movie, downloadClientItem, folderInfo, SceneSource(movie, baseFolder)); - 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; - 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) { @@ -160,34 +169,39 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual if (movie == null) { - var localMovie = new LocalMovie() + var relativeParseInfo = Parser.Parser.ParseMoviePath(relativeFile, false); + + if (relativeParseInfo != null) { - Path = file, - Quality = QualityParser.ParseQuality(file), - Size = _diskProvider.GetFileSize(file) - }; + movie = _movieService.FindByTitle(relativeParseInfo.MovieTitle); + } + } - 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 { file }, - movie, downloadClientItem, null, SceneSource(movie, folder), true); + var importDecisions = _importDecisionMaker.GetImportDecisions(new List {file}, movie, downloadClientItem, null, SceneSource(movie, baseFolder)); if (importDecisions.Any()) { - return MapItem(importDecisions.First(), folder, downloadId, null); + return MapItem(importDecisions.First(), rootFolder, downloadId, null); } return new ManualImportItem { DownloadId = downloadId, Path = file, - RelativePath = folder.GetRelativePath(file), + RelativePath = rootFolder.GetRelativePath(file), Name = Path.GetFileNameWithoutExtension(file), - Rejections = new List - { - new Rejection("Unable to process file") - } + Rejections = new List() }; } @@ -232,15 +246,14 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual var file = message.Files[i]; var movie = _movieService.GetMovie(file.MovieId); - var parsedMovieInfo = _parsingService.ParseMoviePathInfo(file.Path, new List()) ?? new ParsedMovieInfo(); - var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path); + var fileMovieInfo = Parser.Parser.ParseMoviePath(file.Path, false) ?? new ParsedMovieInfo(); var existingFile = movie.Path.IsParentPath(file.Path); + TrackedDownload trackedDownload = null; var localMovie = new LocalMovie { ExistingFile = false, - MediaInfo = mediaInfo, - ParsedMovieInfo = parsedMovieInfo, + FileMovieInfo = fileMovieInfo, Path = file.Path, Quality = file.Quality, Languages = file.Languages, @@ -248,26 +261,42 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual 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 var importDecision = new ImportDecision(localMovie); - if (file.DownloadId.IsNullOrWhiteSpace()) + if (trackedDownload == null) { imported.AddRange(_importApprovedMovie.Import(new List { importDecision }, !existingFile, null, message.ImportMode)); } else { - var trackedDownload = _trackedDownloadService.Find(file.DownloadId); var importResult = _importApprovedMovie.Import(new List { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First(); imported.Add(importResult); importedTrackedDownload.Add(new ManuallyImportedFile - { - TrackedDownload = trackedDownload, - ImportResult = importResult - }); + { + TrackedDownload = trackedDownload, + ImportResult = importResult + }); } } diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Specifications/NotSampleSpecification.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Specifications/NotSampleSpecification.cs index eee42c7e7..89fda7fb0 100644 --- a/src/NzbDrone.Core/MediaFiles/MovieImport/Specifications/NotSampleSpecification.cs +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Specifications/NotSampleSpecification.cs @@ -17,15 +17,13 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Specifications _logger = logger; } - public Decision IsSatisfiedBy(LocalMovie localEpisode, DownloadClientItem downloadClientItem) + public Decision IsSatisfiedBy(LocalMovie localMovie, DownloadClientItem downloadClientItem) { - var sample = _detectSample.IsSample(localEpisode.Movie, - localEpisode.Quality, - localEpisode.Path, - localEpisode.Size, + var sample = _detectSample.IsSample(localMovie.Movie, + localMovie.Path, false); - if (sample) + if (sample == DetectSampleResult.Sample) { return Decision.Reject("Sample"); } diff --git a/src/NzbDrone.Core/Movies/MovieRepository.cs b/src/NzbDrone.Core/Movies/MovieRepository.cs index deea1cf6d..7d11891f5 100644 --- a/src/NzbDrone.Core/Movies/MovieRepository.cs +++ b/src/NzbDrone.Core/Movies/MovieRepository.cs @@ -27,6 +27,7 @@ namespace NzbDrone.Core.Movies List GetMoviesByFileId(int fileId); void SetFileId(int fileId, int movieId); PagingSpec MoviesWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff); + Movie FindByPath(string path); } public class MovieRepository : BasicRepository, IMovieRepository @@ -103,61 +104,6 @@ namespace NzbDrone.Core.Movies return pagingSpec; } - /*public override PagingSpec GetPaged(PagingSpec 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($"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("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 GetPagedQuery(QueryBuilder query, PagingSpec pagingSpec) - { - return DataMapper.Query().Join(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 GetPagedQuery(QueryBuilder query, PagingSpec pagingSpec) - { - var newQuery = base.GetPagedQuery(query.Join(JoinType.Left, m => m.JoinAlternativeTitles, (movie, title) => title.MovieId == movie.Id), pagingSpec); - System.Console.WriteLine(newQuery.ToString()); - return newQuery; - }*/ - public SortBuilder GetMoviesWithoutFilesQuery(PagingSpec pagingSpec) { return Query.Where(pagingSpec.FilterExpressions.FirstOrDefault()) @@ -242,15 +188,6 @@ namespace NzbDrone.Core.Movies if (result == null) { - /*IEnumerable movies = All(); - Func titleCleaner = title => CoreParser.CleanSeriesTitle(title.ToLower()); - Func, 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(JoinType.Inner, m => m.AlternativeTitles, (m, t) => m.Id == t.MovieId) .Where(t => t.CleanTitle == cleanTitle || t.CleanTitle == cleanTitleWithArabicNumbers || t.CleanTitle == cleanTitleWithRomanNumbers) @@ -260,12 +197,6 @@ namespace NzbDrone.Core.Movies } return result; - - /*return year.HasValue - ? results?.FirstOrDefault(movie => movie.Year == year.Value) - - - : results?.FirstOrDefault();*/ } public Movie FindByTmdbId(int tmdbid) @@ -273,6 +204,12 @@ namespace NzbDrone.Core.Movies return Query.Where(m => m.TmdbId == tmdbid).FirstOrDefault(); } + public Movie FindByPath(string path) + { + return Query.Where(s => s.Path == path) + .FirstOrDefault(); + } + protected override QueryBuilder AddJoinQueries(QueryBuilder baseQuery) { baseQuery = base.AddJoinQueries(baseQuery); diff --git a/src/NzbDrone.Core/Movies/MovieService.cs b/src/NzbDrone.Core/Movies/MovieService.cs index b958165e5..c5004599d 100644 --- a/src/NzbDrone.Core/Movies/MovieService.cs +++ b/src/NzbDrone.Core/Movies/MovieService.cs @@ -30,6 +30,7 @@ namespace NzbDrone.Core.Movies Movie FindByTitle(string title, int year); Movie FindByTitleInexact(string title, int? year); Movie FindByTitleSlug(string slug); + Movie FindByPath(string path); bool MovieExists(Movie movie); Movie GetMovieByFileId(int fileId); List GetMoviesBetweenDates(DateTime start, DateTime end, bool includeUnmonitored); @@ -287,6 +288,11 @@ namespace NzbDrone.Core.Movies 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) { var movie = _movieRepository.Get(movieId); diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index cb3a40495..0e733ca16 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -205,6 +205,20 @@ + + + + + + + + + + + + + + @@ -1078,6 +1092,8 @@ + + diff --git a/src/NzbDrone.Core/Organizer/FileNameValidation.cs b/src/NzbDrone.Core/Organizer/FileNameValidation.cs index 414776818..9534259e2 100644 --- a/src/NzbDrone.Core/Organizer/FileNameValidation.cs +++ b/src/NzbDrone.Core/Organizer/FileNameValidation.cs @@ -6,42 +6,6 @@ namespace NzbDrone.Core.Organizer { 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 ValidEpisodeFormat(this IRuleBuilder ruleBuilder) - { - ruleBuilder.SetValidator(new NotEmptyValidator(null)); - return ruleBuilder.SetValidator(new ValidStandardEpisodeFormatValidator()); - } - - public static IRuleBuilderOptions ValidDailyEpisodeFormat(this IRuleBuilder ruleBuilder) - { - ruleBuilder.SetValidator(new NotEmptyValidator(null)); - return ruleBuilder.SetValidator(new ValidDailyEpisodeFormatValidator()); - } - - public static IRuleBuilderOptions ValidAnimeEpisodeFormat(this IRuleBuilder ruleBuilder) - { - ruleBuilder.SetValidator(new NotEmptyValidator(null)); - return ruleBuilder.SetValidator(new ValidAnimeEpisodeFormatValidator()); - } - - public static IRuleBuilderOptions ValidSeriesFolderFormat(this IRuleBuilder ruleBuilder) - { - ruleBuilder.SetValidator(new NotEmptyValidator(null)); - return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.SeriesTitleRegex)).WithMessage("Must contain movie title"); - } - - public static IRuleBuilderOptions ValidSeasonFolderFormat(this IRuleBuilder ruleBuilder) - { - ruleBuilder.SetValidator(new NotEmptyValidator(null)); - return ruleBuilder.SetValidator(new RegularExpressionValidator(SeasonFolderRegex)).WithMessage("Must contain season number"); - } - public static IRuleBuilderOptions ValidMovieFolderFormat(this IRuleBuilder ruleBuilder) { 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"); } } - - 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; - } - } } diff --git a/src/NzbDrone.Core/Parser/Augmenters/AugmentWithMediaInfo.cs b/src/NzbDrone.Core/Parser/Augmenters/AugmentWithMediaInfo.cs index 425a297ab..6a7493b2a 100644 --- a/src/NzbDrone.Core/Parser/Augmenters/AugmentWithMediaInfo.cs +++ b/src/NzbDrone.Core/Parser/Augmenters/AugmentWithMediaInfo.cs @@ -50,7 +50,7 @@ namespace NzbDrone.Core.Parser.Augmenters if (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; } } diff --git a/src/NzbDrone.Core/Parser/Model/LocalMovie.cs b/src/NzbDrone.Core/Parser/Model/LocalMovie.cs index a9e7d3c6b..9d3dacfba 100644 --- a/src/NzbDrone.Core/Parser/Model/LocalMovie.cs +++ b/src/NzbDrone.Core/Parser/Model/LocalMovie.cs @@ -15,13 +15,18 @@ namespace NzbDrone.Core.Parser.Model public string Path { 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 QualityModel Quality { get; set; } public List Languages { get; set; } public MediaInfoModel MediaInfo { 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() { diff --git a/src/NzbDrone.Core/Parser/Model/ParsedMovieInfo.cs b/src/NzbDrone.Core/Parser/Model/ParsedMovieInfo.cs index 6c3a6099c..bbcd0989e 100644 --- a/src/NzbDrone.Core/Parser/Model/ParsedMovieInfo.cs +++ b/src/NzbDrone.Core/Parser/Model/ParsedMovieInfo.cs @@ -28,10 +28,7 @@ namespace NzbDrone.Core.Parser.Model /// [JsonIgnore] public IDictionary ExtraInfo = new Dictionary(); - //public int SeasonNumber { get; set; } public List Languages = new List(); - //public bool FullSeason { get; set; } - //public bool Special { get; set; } public string ReleaseGroup { get; set; } public string ReleaseHash { get; set; } public string Edition { get; set;} diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index 64491a662..876e02a27 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -149,7 +149,7 @@ namespace NzbDrone.Core.Parser {"ü", "ue"}, }; - private static ParsedMovieInfo ParseMoviePath(string path, bool isLenient) + public static ParsedMovieInfo ParseMoviePath(string path, bool isLenient) { var fileInfo = new FileInfo(path); diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs index e660b3230..1868cfaa8 100644 --- a/src/NzbDrone.Core/Parser/ParsingService.cs +++ b/src/NzbDrone.Core/Parser/ParsingService.cs @@ -22,11 +22,9 @@ namespace NzbDrone.Core.Parser { public interface IParsingService { - LocalMovie GetLocalMovie(string filename, ParsedMovieInfo minimalInfo, Movie movie, List helpers, bool sceneSource = false); Movie GetMovie(string title); MappingResult Map(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria = null); ParsedMovieInfo ParseMovieInfo(string title, List helpers); - ParsedMovieInfo ParseMoviePathInfo(string path, List helpers); ParsedMovieInfo ParseMinimalMovieInfo(string path, bool isDir = false); ParsedMovieInfo ParseMinimalPathMovieInfo(string path); List ParseCustomFormat(ParsedMovieInfo movieInfo); @@ -122,27 +120,6 @@ namespace NzbDrone.Core.Parser return minimalInfo; } - public ParsedMovieInfo ParseMoviePathInfo(string path, List 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 ParseCustomFormat(ParsedMovieInfo movieInfo) { var matches = MatchFormatTags(movieInfo); @@ -178,22 +155,6 @@ namespace NzbDrone.Core.Parser return matches; } - public LocalMovie GetLocalMovie(string filename, ParsedMovieInfo minimalInfo, Movie movie, List 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) { return Parser.ParseMovieTitle(file, _config.ParsingLeniency > 0, isDir); diff --git a/src/NzbDrone.Core/Parser/QualityParser.cs b/src/NzbDrone.Core/Parser/QualityParser.cs index 3e8108dac..cb14e1bde 100644 --- a/src/NzbDrone.Core/Parser/QualityParser.cs +++ b/src/NzbDrone.Core/Parser/QualityParser.cs @@ -349,7 +349,7 @@ namespace NzbDrone.Core.Parser result.Source = MediaFileExtensions.GetSourceForExtension(Path.GetExtension(name)); result.Resolution = MediaFileExtensions.GetResolutionForExtension(Path.GetExtension(name)); - result.QualitySource = QualitySource.Extension; + result.QualityDetectionSource = QualityDetectionSource.Extension; } catch (ArgumentException) { diff --git a/src/NzbDrone.Core/Qualities/QualityDetectionSource.cs b/src/NzbDrone.Core/Qualities/QualityDetectionSource.cs new file mode 100644 index 000000000..3f7695214 --- /dev/null +++ b/src/NzbDrone.Core/Qualities/QualityDetectionSource.cs @@ -0,0 +1,9 @@ +namespace NzbDrone.Core.Qualities +{ + public enum QualityDetectionSource + { + Name, + Extension, + MediaInfo + } +} diff --git a/src/NzbDrone.Core/Qualities/QualityFinder.cs b/src/NzbDrone.Core/Qualities/QualityFinder.cs new file mode 100644 index 000000000..c19b444b7 --- /dev/null +++ b/src/NzbDrone.Core/Qualities/QualityFinder.cs @@ -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; + } + } +} diff --git a/src/NzbDrone.Core/Qualities/QualityModel.cs b/src/NzbDrone.Core/Qualities/QualityModel.cs index cab450b20..99a033e86 100644 --- a/src/NzbDrone.Core/Qualities/QualityModel.cs +++ b/src/NzbDrone.Core/Qualities/QualityModel.cs @@ -25,7 +25,7 @@ namespace NzbDrone.Core.Qualities public string HardcodedSubs { get; set; } [JsonIgnore] - public QualitySource QualitySource { get; set; } + public QualityDetectionSource QualityDetectionSource { get; set; } public QualityModel() : this(Quality.Unknown, new Revision()) diff --git a/src/NzbDrone.Core/Qualities/QualitySource.cs b/src/NzbDrone.Core/Qualities/QualitySource.cs index 5c0c2c81f..9610c3d38 100644 --- a/src/NzbDrone.Core/Qualities/QualitySource.cs +++ b/src/NzbDrone.Core/Qualities/QualitySource.cs @@ -2,8 +2,12 @@ { public enum QualitySource { - Name, - Extension, - MediaInfo + Unknown, + Television, + TelevisionRaw, + Web, + WebRip, + DVD, + Bluray } } diff --git a/src/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs b/src/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs index 710d8d6d4..0ca1d8074 100644 --- a/src/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs +++ b/src/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs @@ -5,8 +5,8 @@ namespace NzbDrone.Core.Update.Commands public class ApplicationUpdateCommand : Command { 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; } } diff --git a/src/Radarr.Http/TinyIoCNancyBootstrapper.cs b/src/Radarr.Http/TinyIoCNancyBootstrapper.cs index d713eb8db..bd065547b 100644 --- a/src/Radarr.Http/TinyIoCNancyBootstrapper.cs +++ b/src/Radarr.Http/TinyIoCNancyBootstrapper.cs @@ -91,7 +91,6 @@ namespace Radarr.Http break; case Lifetime.PerRequest: throw new InvalidOperationException("Unable to directly register a per request lifetime."); - break; default: throw new ArgumentOutOfRangeException(); } @@ -118,7 +117,6 @@ namespace Radarr.Http break; case Lifetime.PerRequest: throw new InvalidOperationException("Unable to directly register a per request lifetime."); - break; default: throw new ArgumentOutOfRangeException(); }