New: Use MediaInfo on File Parsing

This commit is contained in:
Qstick 2019-07-15 22:27:35 -04:00
parent ada9b944dc
commit bfc467dd96
51 changed files with 1092 additions and 689 deletions

View File

@ -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)
};
}

View File

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

View File

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

View File

@ -22,7 +22,7 @@ namespace NzbDrone.Core.Test.MediaFiles
{
[TestFixture]
//TODO: Update all of this for movies.
public class ImportApprovedEpisodesFixture : CoreTest<ImportApprovedMovie>
public class ImportApprovedMoviesFixture : CoreTest<ImportApprovedMovie>
{
private List<ImportDecision> _rejectedDecisions;
private List<ImportDecision> _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"
}));

View File

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

View File

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

View File

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

View File

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

View File

@ -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<DetectSample>
public class DetectSampleFixture : CoreTest<DetectSample>
{
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<IVideoFileInfoReader>().Verify(c => c.GetRunTime(It.IsAny<string>()), Times.Never());
}
@ -61,7 +58,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
{
_localMovie.Path = @"C:\Test\some.show.s01e01.strm";
ShouldBeFalse();
ShouldBeNotSample();
Mocker.GetMock<IVideoFileInfoReader>().Verify(c => c.GetRunTime(It.IsAny<string>()), Times.Never());
}
@ -70,12 +67,9 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
public void should_use_runtime()
{
GivenRuntime(120);
GivenFileSize(1000.Megabytes());
Subject.IsSample(_localMovie.Movie,
_localMovie.Quality,
_localMovie.Path,
_localMovie.Size,
false);
Mocker.GetMock<IVideoFileInfoReader>().Verify(v => v.GetRunTime(It.IsAny<string>()), 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<IVideoFileInfoReader>()
.Setup(s => s.GetRunTime(It.IsAny<string>()))
.Throws<DllNotFoundException>();
_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()
{
_movie.Runtime = 2;
GivenRuntime(10);
ShouldBeSample();
}
[Test]
public void should_return_indeterminate_if_mediainfo_result_is_null()
{
Mocker.GetMock<IVideoFileInfoReader>()
.Setup(s => s.GetRunTime(It.IsAny<string>()))
.Throws<DllNotFoundException>();
.Returns((TimeSpan?)null);
GivenFileSize(1.Megabytes());
ShouldBeTrue();
}
private void ShouldBeTrue()
{
Subject.IsSample(_localMovie.Movie,
_localMovie.Quality,
_localMovie.Path,
_localMovie.Size,
false).Should().BeTrue();
}
private void ShouldBeFalse()
{
Subject.IsSample(_localMovie.Movie,
_localMovie.Quality,
_localMovie.Path,
_localMovie.Size,
false).Should().BeFalse();
false).Should().Be(DetectSampleResult.Indeterminate);
ExceptionVerification.ExpectedErrors(1);
}
private void ShouldBeSample()
{
Subject.IsSample(_localMovie.Movie,
_localMovie.Path,
false).Should().Be(DetectSampleResult.Sample);
}
private void ShouldBeNotSample()
{
Subject.IsSample(_localMovie.Movie,
_localMovie.Path,
false).Should().Be(DetectSampleResult.NotSample);
}
}
}

View File

@ -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<IParsingService>()
.Setup(c => c.GetLocalMovie(It.IsAny<string>(), It.IsAny<ParsedMovieInfo>(), It.IsAny<Movie>(), It.IsAny<List<object>>(), It.IsAny<bool>()))
.Returns(_localMovie);
Mocker.GetMock<IParsingService>()
.Setup(c => c.ParseMinimalPathMovieInfo(It.IsAny<string>()))
.Returns(_fileInfo);
@ -100,6 +97,16 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
.Returns(_videoFiles);
}
private void GivenAugmentationSuccess()
{
Mocker.GetMock<IAggregationService>()
.Setup(s => s.Augment(It.IsAny<LocalMovie>(), It.IsAny<bool>()))
.Callback<LocalMovie, bool>((localMovie, otherFiles) =>
{
localMovie.Movie = _localMovie.Movie;
});
}
[Test]
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<LocalMovie>(), downloadClientItem), Times.Once());
_fail2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), downloadClientItem), Times.Once());
_fail3.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), downloadClientItem), Times.Once());
_pass1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), downloadClientItem), Times.Once());
_pass2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), downloadClientItem), Times.Once());
_pass3.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), downloadClientItem), Times.Once());
}
[Test]
@ -160,8 +167,8 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
{
GivenSpecifications(_pass1);
Mocker.GetMock<IParsingService>()
.Setup(c => c.GetLocalMovie(It.IsAny<string>(), It.IsAny<ParsedMovieInfo>(), It.IsAny<Movie>(), It.IsAny<List<object>>(), It.IsAny<bool>()))
Mocker.GetMock<IAggregationService>()
.Setup(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<bool>()))
.Throws<TestException>();
_videoFiles = new List<string>
@ -175,8 +182,8 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
Subject.GetImportDecisions(_videoFiles, _movie);
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalMovie(It.IsAny<string>(), It.IsAny<ParsedMovieInfo>(), It.IsAny<Movie>(), It.IsAny<List<object>>(), It.IsAny<bool>()), Times.Exactly(_videoFiles.Count));
Mocker.GetMock<IAggregationService>()
.Verify(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<bool>()), 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<IParsingService>()
.Verify(
c => c.GetLocalMovie(It.IsAny<string>(),
It.Is<ParsedMovieInfo>(p => fileNames.Contains(p.SimpleReleaseTitle)), It.IsAny<Movie>(),
It.IsAny<List<object>>(), It.IsAny<bool>()), Times.Exactly(_videoFiles.Count));
}
[Test]
public void should_use_file_quality_if_folder_quality_is_null()
{
GivenSpecifications(_pass1, _pass2, _pass3);
var result = Subject.GetImportDecisions(_videoFiles, _movie);
result.Single().LocalMovie.Quality.Should().Be(_fileInfo.Quality);
}
[Test]
public void should_use_file_quality_if_file_quality_was_determined_by_name()
{
GivenSpecifications(_pass1, _pass2, _pass3);
var result = Subject.GetImportDecisions(_videoFiles, _movie, null, new ParsedMovieInfo{Quality = new QualityModel(Quality.SDTV)}, true);
result.Single().LocalMovie.Quality.Should().Be(_fileInfo.Quality);
}
[Test]
public void should_use_folder_quality_when_file_quality_was_determined_by_the_extension()
{
GivenSpecifications(_pass1, _pass2, _pass3);
GivenVideoFiles(new string[] { @"C:\Test\Unsorted\The.Office.S03E115.mkv".AsOsAgnostic() });
_localMovie.Path = _videoFiles.Single();
_localMovie.Quality.QualitySource = QualitySource.Extension;
_localMovie.Quality.Quality = Quality.HDTV720p;
var expectedQuality = new QualityModel(Quality.SDTV);
var result = Subject.GetImportDecisions(_videoFiles, _movie, null, new ParsedMovieInfo { Quality = expectedQuality }, true);
result.Single().LocalMovie.Quality.Should().Be(expectedQuality);
}
[Test]
public void should_use_folder_quality_when_greater_than_file_quality()
{
GivenSpecifications(_pass1, _pass2, _pass3);
GivenVideoFiles(new string[] { @"C:\Test\Unsorted\The.Office.S03E115.mkv".AsOsAgnostic() });
_localMovie.Path = _videoFiles.Single();
_localMovie.Quality.Quality = Quality.HDTV720p;
var expectedQuality = new QualityModel(Quality.Bluray720p);
var result = Subject.GetImportDecisions(_videoFiles, _movie, null, new ParsedMovieInfo { Quality = expectedQuality }, true);
result.Single().LocalMovie.Quality.Should().Be(expectedQuality);
}
[Test]
public void should_not_use_folder_quality_when_it_is_unknown()
{
GivenSpecifications(_pass1, _pass2, _pass3);
_movie.Profile = new Profile
{
Items = Qualities.QualityFixture.GetDefaultQualities(Quality.DVD, Quality.Unknown)
};
var folderQuality = new QualityModel(Quality.Unknown);
var result = Subject.GetImportDecisions(_videoFiles, _movie, null, new ParsedMovieInfo { Quality = folderQuality}, true);
result.Single().LocalMovie.Quality.Should().Be(_quality);
Mocker.GetMock<IAggregationService>()
.Setup(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<bool>()))
.Throws<TestException>();
}
[Test]
public void should_return_a_decision_when_exception_is_caught()
{
Mocker.GetMock<IParsingService>()
.Setup(c => c.GetLocalMovie(It.IsAny<string>(), It.IsAny<ParsedMovieInfo>(), It.IsAny<Movie>(), It.IsAny<List<object>>(), It.IsAny<bool>()))
Mocker.GetMock<IAggregationService>()
.Setup(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<bool>()))
.Throws<TestException>();
_videoFiles = new List<string>

View File

@ -18,7 +18,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Specifications
{
_localMovie = Builder<LocalMovie>.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<ParsedMovieInfo>.CreateNew()
.Build())
.Build();

View File

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

View File

@ -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);
}
}
}

View File

@ -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")]

View File

@ -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);

View File

@ -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<string>() { fileInfo.FullName }, movie, downloadClientItem, null, true, false);
var decisions = _importDecisionMaker.GetImportDecisions(new List<string>() { fileInfo.FullName }, movie, downloadClientItem, null, true);
return _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode);
}

View File

@ -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)
{
}
}
}

View File

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

View File

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

View File

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

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

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

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<Quality> _largeSampleSizeQualities = new List<Quality> { Quality.HDTV1080p, Quality.WEBDL1080p, Quality.Bluray1080p };
private static List<Resolution> _largeSampleSizeResolutions = new List<Resolution>{Resolution.R1080P, Resolution.R2160P};
public DetectSample(IVideoFileInfoReader videoFileInfoReader, Logger logger)
{
_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)
{
_logger.Debug("Falling back to file size detection");
var minimumRuntime = GetMinimumAllowedRuntime(movie);
return CheckSize(size, quality);
if (runTime.Value.TotalMinutes.Equals(0))
{
_logger.Error("[{0}] has a runtime of 0, is it a valid video file?", path);
return DetectSampleResult.Sample;
}
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;
}
private bool CheckSize(long size, QualityModel quality)
{
if (_largeSampleSizeResolutions.Contains(quality.Resolution))
{
if (size < SampleSizeLimit * 2)
{
_logger.Debug("1080p file is less than sample limit");
return true;
}
}
if (size < SampleSizeLimit)
{
_logger.Debug("File is less than sample limit");
return true;
}
return false;
return DetectSampleResult.NotSample;
}
private int GetMinimumAllowedRuntime(Movie movie)
{
if (movie.Runtime < 1)
//Anime short - 15 seconds
if (movie.Runtime <= 3)
{
return 5 * 60;
return 15;
}
return movie.Runtime / 5 * 60;
//Webisodes - 90 seconds
if (movie.Runtime <= 10)
{
return 90;
}
//30 minute episodes - 5 minutes
if (movie.Runtime <= 30)
{
return 300;
}
//60 minute episodes - 10 minutes
return 600;
}
}
}

View File

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

View File

@ -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)

View File

@ -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<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie);
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, bool shouldCheckQuality);
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource, bool shouldCheckQuality);
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource);
}
public class ImportDecisionMaker : IMakeImportDecision
{
private readonly IEnumerable<IImportDecisionEngineSpecification> _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<IImportDecisionEngineSpecification> 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<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie)
{
return GetImportDecisions(videoFiles, movie, null, null, true, false);
}
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, bool shouldCheckQuality = false)
{
return GetImportDecisions(videoFiles, movie, null, null, true, shouldCheckQuality);
return GetImportDecisions(videoFiles, movie, null, null, true);
}
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource)
@ -82,102 +73,79 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
_logger.Debug("Analyzing {0}/{1} files.", newFiles.Count, videoFiles.Count());
var shouldUseFolderName = ShouldUseFolderName(videoFiles, movie, folderInfo);
ParsedMovieInfo downloadClientItemInfo = null;
if (downloadClientItem != null)
{
downloadClientItemInfo = Parser.Parser.ParseMovieTitle(downloadClientItem.Title, false);
}
var nonSampleVideoFileCount = GetNonSampleVideoFileCount(newFiles, movie, downloadClientItemInfo, folderInfo);
var decisions = new List<ImportDecision>();
foreach (var file in newFiles)
{
decisions.AddIfNotNull(GetDecision(file, movie, downloadClientItem, folderInfo, sceneSource, shouldUseFolderName));
var localMovie = new LocalMovie
{
Movie = movie,
DownloadClientMovieInfo = downloadClientItemInfo,
FolderMovieInfo = folderInfo,
Path = file,
SceneSource = sceneSource
};
decisions.AddIfNotNull(GetDecision(localMovie, downloadClientItem, nonSampleVideoFileCount > 1));
}
return decisions;
}
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource, bool shouldCheckQuality)
{
var newFiles = _mediaFileService.FilterExistingFiles(videoFiles.ToList(), movie);
_logger.Debug("Analyzing {0}/{1} files.", newFiles.Count, videoFiles.Count());
var shouldUseFolderName = ShouldUseFolderName(videoFiles, movie, folderInfo);
var decisions = new List<ImportDecision>();
foreach (var file in newFiles)
{
decisions.AddIfNotNull(GetDecision(file, movie, downloadClientItem, folderInfo, sceneSource, shouldUseFolderName, shouldCheckQuality));
}
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;
var fileMovieInfo = Parser.Parser.ParseMoviePath(localMovie.Path, false);
localMovie.FileMovieInfo = fileMovieInfo;
localMovie.Size = _diskProvider.GetFileSize(localMovie.Path);
try
{
ParsedMovieInfo modifiedFolderInfo = null;
if (folderInfo != null)
_aggregationService.Augment(localMovie, otherFiles);
if (localMovie.Movie == null)
{
modifiedFolderInfo = folderInfo.JsonClone();
// We want the filename to be used for parsing quality, etc. even if we didn't get any movie info from there.
modifiedFolderInfo.SimpleReleaseTitle = Path.GetFileName(file);
}
var minimalInfo = _parsingService.ParseMinimalPathMovieInfo(file) ?? modifiedFolderInfo;
LocalMovie localMovie = null;
if (minimalInfo != 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<object>{mediaInfo, firstHistoryItem, sizeMovie, folderInfo}, sceneSource);
localMovie.Quality = GetQuality(folderInfo, localMovie.Quality, movie);
localMovie.Size = size;
_logger.Debug("Size: {0}", localMovie.Size);
decision = GetDecision(localMovie, downloadClientItem);
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)
{
_logger.Error(e, "Couldn't import file. " + file);
decision = new ImportDecision(localMovie, new Rejection("Unable to parse file"));
}
catch (Exception ex)
{
_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<string> videoFiles, Movie movie, ParsedMovieInfo folderInfo)
private int GetNonSampleVideoFileCount(List<string> videoFiles, Movie movie, ParsedMovieInfo downloadClientItemInfo, ParsedMovieInfo folderInfo)
{
return false;
}
private QualityModel GetQuality(ParsedMovieInfo folderInfo, QualityModel fileQuality, Movie movie)
{
if (UseFolderQuality(folderInfo, fileQuality, movie))
return videoFiles.Count(file =>
{
_logger.Debug("Using quality from folder: {0}", folderInfo.Quality);
return folderInfo.Quality;
}
var sample = _detectSample.IsSample(movie, file, false);
return fileQuality;
}
if (sample == DetectSampleResult.Sample)
{
return false;
}
private bool UseFolderQuality(ParsedMovieInfo folderInfo, QualityModel fileQuality, Movie movie)
{
if (folderInfo == null)
{
return false;
}
if (folderInfo.Quality.Quality == Quality.Unknown)
{
return false;
}
if (fileQuality.QualitySource == QualitySource.Extension)
{
return true;
}
if (fileQuality.QualitySource == QualitySource.MediaInfo)
{
return false;
}
if (new QualityModelComparer(movie.Profile).Compare(folderInfo.Quality, fileQuality) > 0)
{
return true;
}
return false;
});
}
private bool ShouldCheckQualityForParsedQuality(Quality quality)
{
List<Quality> shouldNotCheck = new List<Quality> { Quality.WORKPRINT, Quality.TELECINE, Quality.TELESYNC,
Quality.DVDSCR, Quality.DVD, Quality.CAM, Quality.DVDR, Quality.Remux1080p, Quality.Remux2160p, Quality.REGIONAL
};
if (shouldNotCheck.Contains(quality))
{
return false;
}
return true;
}
}
}

View File

@ -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<ManualImportItem>();
}
return new List<ManualImportItem> { ProcessFile(path, downloadId) };
var rootFolder = Path.GetDirectoryName(path);
return new List<ManualImportItem> { ProcessFile(rootFolder, rootFolder, path, downloadId) };
}
return ProcessFolder(path, downloadId, filterExistingFiles);
return ProcessFolder(path, path, downloadId, filterExistingFiles);
}
private List<ManualImportItem> ProcessFolder(string folder, string downloadId, bool filterExistingFiles)
private List<ManualImportItem> ProcessFolder(string rootFolder, string baseFolder, string downloadId, bool filterExistingFiles)
{
DownloadClientItem downloadClientItem = null;
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));
return files.Select(file => ProcessFile(file, downloadId, folder)).Where(i => i != null).ToList();
movie = _movieService.FindByPath(rootFolder);
}
var historyItems = _historyService.FindByDownloadId(downloadId);
var firstHistoryItem = historyItems.OrderByDescending(h => h.Date).FirstOrDefault();
var folderInfo = _parsingService.ParseMovieInfo(directoryInfo.Name, new List<object>{firstHistoryItem});
var movieFiles = _diskScanService.GetVideoFiles(folder).ToList();
var decisions = _importDecisionMaker.GetImportDecisions(movieFiles, movie, downloadClientItem, folderInfo, SceneSource(movie, folder), false);
if (movie == null)
{
var files = _diskScanService.FilterFiles(baseFolder, _diskScanService.GetVideoFiles(baseFolder, false));
var subfolders = _diskScanService.FilterFiles(baseFolder, _diskProvider.GetDirectories(baseFolder));
return decisions.Select(decision => MapItem(decision, folder, downloadId, directoryInfo.Name)).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 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, 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()
{
Path = file,
Quality = QualityParser.ParseQuality(file),
Size = _diskProvider.GetFileSize(file)
};
var relativeParseInfo = Parser.Parser.ParseMoviePath(relativeFile, false);
return MapItem(new ImportDecision(localMovie, new Rejection("Unknown Movie")), folder, downloadId, null);
if (relativeParseInfo != null)
{
movie = _movieService.FindByTitle(relativeParseInfo.MovieTitle);
}
}
var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> { file },
movie, downloadClientItem, null, SceneSource(movie, folder), true);
if (movie == null)
{
var localMovie = new LocalMovie();
localMovie.Path = file;
localMovie.Quality = QualityParser.ParseQuality(file);
localMovie.Languages = LanguageParser.ParseLanguages(file);
localMovie.Size = _diskProvider.GetFileSize(file);
return MapItem(new ImportDecision(localMovie, new Rejection("Unknown Movie")), rootFolder, downloadId, null);
}
var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> {file}, 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<Rejection>
{
new Rejection("Unable to process file")
}
Rejections = new List<Rejection>()
};
}
@ -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<object>()) ?? 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> { importDecision }, !existingFile, null, message.ImportMode));
}
else
{
var trackedDownload = _trackedDownloadService.Find(file.DownloadId);
var importResult = _importApprovedMovie.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
imported.Add(importResult);
importedTrackedDownload.Add(new ManuallyImportedFile
{
TrackedDownload = trackedDownload,
ImportResult = importResult
});
{
TrackedDownload = trackedDownload,
ImportResult = importResult
});
}
}

View File

@ -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");
}

View File

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

View File

@ -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<Movie> 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);

View File

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

View File

@ -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<T, string> ValidEpisodeFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
{
ruleBuilder.SetValidator(new NotEmptyValidator(null));
return ruleBuilder.SetValidator(new ValidStandardEpisodeFormatValidator());
}
public static IRuleBuilderOptions<T, string> ValidDailyEpisodeFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
{
ruleBuilder.SetValidator(new NotEmptyValidator(null));
return ruleBuilder.SetValidator(new ValidDailyEpisodeFormatValidator());
}
public static IRuleBuilderOptions<T, string> ValidAnimeEpisodeFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
{
ruleBuilder.SetValidator(new NotEmptyValidator(null));
return ruleBuilder.SetValidator(new ValidAnimeEpisodeFormatValidator());
}
public static IRuleBuilderOptions<T, string> ValidSeriesFolderFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
{
ruleBuilder.SetValidator(new NotEmptyValidator(null));
return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.SeriesTitleRegex)).WithMessage("Must contain movie title");
}
public static IRuleBuilderOptions<T, string> ValidSeasonFolderFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
{
ruleBuilder.SetValidator(new NotEmptyValidator(null));
return ruleBuilder.SetValidator(new RegularExpressionValidator(SeasonFolderRegex)).WithMessage("Must contain season number");
}
public static IRuleBuilderOptions<T, string> ValidMovieFolderFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
{
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;
}
}
}

View File

@ -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;
}
}

View File

@ -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<Language> 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()
{

View File

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

View File

@ -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);

View File

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

View File

@ -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)
{

View File

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

View File

@ -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;
}
}
}

View File

@ -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())

View File

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

View File

@ -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;
}
}

View File

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