New: Use media info during import to extract resolution for quality

Closes #448
Closes #1105
pull/2463/head
Mark McDowall 6 years ago committed by Taloth
parent 650d18797a
commit 81e385bebf

@ -0,0 +1,109 @@
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
{
[TestFixture]
public class AugmentEpisodesFixture : CoreTest<AggregateEpisodes>
{
private Series _series;
[SetUp]
public void Setup()
{
_series = Builder<Series>.CreateNew().Build();
var augmenters = new List<Mock<IAggregateLocalEpisode>>
{
new Mock<IAggregateLocalEpisode>()
};
Mocker.SetConstant(augmenters.Select(c => c.Object));
}
[Test]
public void should_not_use_folder_for_full_season()
{
var fileEpisodeInfo = Parser.Parser.ParseTitle("Series.Title.S01E01");
var folderEpisodeInfo = Parser.Parser.ParseTitle("Series.Title.S01");
var localEpisode = new LocalEpisode
{
FileEpisodeInfo = fileEpisodeInfo,
FolderEpisodeInfo = folderEpisodeInfo,
Path = @"C:\Test\Unsorted TV\Series.Title.S01\Series.Title.S01E01.mkv".AsOsAgnostic(),
Series = _series
};
Subject.Aggregate(localEpisode, false);
Mocker.GetMock<IParsingService>()
.Verify(v => v.GetEpisodes(fileEpisodeInfo, _series, localEpisode.SceneSource, null), Times.Once());
}
[Test]
public void should_not_use_folder_when_it_contains_more_than_one_valid_video_file()
{
var fileEpisodeInfo = Parser.Parser.ParseTitle("Series.Title.S01E01");
var folderEpisodeInfo = Parser.Parser.ParseTitle("Series.Title.S01");
var localEpisode = new LocalEpisode
{
FileEpisodeInfo = fileEpisodeInfo,
FolderEpisodeInfo = folderEpisodeInfo,
Path = @"C:\Test\Unsorted TV\Series.Title.S01\Series.Title.S01E01.mkv".AsOsAgnostic(),
Series = _series
};
Subject.Aggregate(localEpisode, true);
Mocker.GetMock<IParsingService>()
.Verify(v => v.GetEpisodes(fileEpisodeInfo, _series, localEpisode.SceneSource, null), Times.Once());
}
[Test]
public void should_not_use_folder_name_if_file_name_is_scene_name()
{
var fileEpisodeInfo = Parser.Parser.ParseTitle("Series.Title.S01E01");
var folderEpisodeInfo = Parser.Parser.ParseTitle("Series.Title.S01E01");
var localEpisode = new LocalEpisode
{
FileEpisodeInfo = fileEpisodeInfo,
FolderEpisodeInfo = folderEpisodeInfo,
Path = @"C:\Test\Unsorted TV\Series.Title.S01E01\Series.Title.S01E01.720p.HDTV-Sonarr.mkv".AsOsAgnostic(),
Series = _series
};
Subject.Aggregate(localEpisode, false);
Mocker.GetMock<IParsingService>()
.Verify(v => v.GetEpisodes(fileEpisodeInfo, _series, localEpisode.SceneSource, null), Times.Once());
}
[Test]
public void should_use_folder_when_only_one_video_file()
{
var fileEpisodeInfo = Parser.Parser.ParseTitle("Series.Title.S01E01");
var folderEpisodeInfo = Parser.Parser.ParseTitle("Series.Title.S01E01");
var localEpisode = new LocalEpisode
{
FileEpisodeInfo = fileEpisodeInfo,
FolderEpisodeInfo = folderEpisodeInfo,
Path = @"C:\Test\Unsorted TV\Series.Title.S01E01\Series.Title.S01E01.mkv".AsOsAgnostic(),
Series = _series
};
Subject.Aggregate(localEpisode, false);
Mocker.GetMock<IParsingService>()
.Verify(v => v.GetEpisodes(folderEpisodeInfo, _series, localEpisode.SceneSource, null), Times.Once());
}
}
}

@ -0,0 +1,93 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators;
using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators.Augmenters.Quality;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.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<LocalEpisode>()))
.Returns(AugmentQualityResult.ResolutionOnly(1080, Confidence.MediaInfo));
_fileExtensionAugmenter.Setup(s => s.AugmentQuality(It.IsAny<LocalEpisode>()))
.Returns(new AugmentQualityResult(QualitySource.Television, Confidence.Fallback, 720, Confidence.Fallback, new Revision()));
_nameAugmenter.Setup(s => s.AugmentQuality(It.IsAny<LocalEpisode>()))
.Returns(new AugmentQualityResult(QualitySource.Television, Confidence.Default, 480, 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<LocalEpisode>()))
.Returns<LocalEpisode>(l => null);
GivenAugmenters(_fileExtensionAugmenter, nullMock);
var result = Subject.Aggregate(new LocalEpisode(), 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 LocalEpisode(), 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 LocalEpisode(), 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 LocalEpisode(), false);
result.Quality.QualityDetectionSource.Should().Be(QualityDetectionSource.MediaInfo);
result.Quality.Quality.Should().Be(Quality.HDTV1080p);
}
}
}

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

@ -6,7 +6,6 @@ using NUnit.Framework;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
@ -15,6 +14,7 @@ using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
using FizzWare.NBuilder;
using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation;
namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
{
@ -67,10 +67,6 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
Path = @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi"
};
Mocker.GetMock<IParsingService>()
.Setup(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<ParsedEpisodeInfo>(), It.IsAny<bool>()))
.Returns(_localEpisode);
GivenVideoFiles(new List<string> { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi".AsOsAgnostic() });
}
@ -88,20 +84,31 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
.Returns(_videoFiles);
}
private void GivenAugmentationSuccess()
{
Mocker.GetMock<IAugmentingService>()
.Setup(s => s.Augment(It.IsAny<LocalEpisode>(), It.IsAny<bool>()))
.Callback<LocalEpisode, bool>((localEpisode, otherFiles) =>
{
localEpisode.Episodes = _localEpisode.Episodes;
});
}
[Test]
public void should_call_all_specifications()
{
var downloadClientItem = Builder<DownloadClientItem>.CreateNew().Build();
GivenAugmentationSuccess();
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
Subject.GetImportDecisions(_videoFiles, new Series(), downloadClientItem, null, false);
_fail1.Verify(c => c.IsSatisfiedBy(_localEpisode, downloadClientItem), Times.Once());
_fail2.Verify(c => c.IsSatisfiedBy(_localEpisode, downloadClientItem), Times.Once());
_fail3.Verify(c => c.IsSatisfiedBy(_localEpisode, downloadClientItem), Times.Once());
_pass1.Verify(c => c.IsSatisfiedBy(_localEpisode, downloadClientItem), Times.Once());
_pass2.Verify(c => c.IsSatisfiedBy(_localEpisode, downloadClientItem), Times.Once());
_pass3.Verify(c => c.IsSatisfiedBy(_localEpisode, downloadClientItem), Times.Once());
_fail1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>(), downloadClientItem), Times.Once());
_fail2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>(), downloadClientItem), Times.Once());
_fail3.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>(), downloadClientItem), Times.Once());
_pass1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>(), downloadClientItem), Times.Once());
_pass2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>(), downloadClientItem), Times.Once());
_pass3.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>(), downloadClientItem), Times.Once());
}
[Test]
@ -125,8 +132,9 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
}
[Test]
public void should_return_pass_if_all_specs_pass()
public void should_return_approved_if_all_specs_pass()
{
GivenAugmentationSuccess();
GivenSpecifications(_pass1, _pass2, _pass3);
var result = Subject.GetImportDecisions(_videoFiles, new Series());
@ -137,6 +145,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
[Test]
public void should_have_same_number_of_rejections_as_specs_that_failed()
{
GivenAugmentationSuccess();
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
var result = Subject.GetImportDecisions(_videoFiles, new Series());
@ -148,8 +157,8 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
{
GivenSpecifications(_pass1);
Mocker.GetMock<IParsingService>()
.Setup(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<ParsedEpisodeInfo>(), It.IsAny<bool>()))
Mocker.GetMock<IAugmentingService>()
.Setup(c => c.Augment(It.IsAny<LocalEpisode>(), It.IsAny<bool>()))
.Throws<TestException>();
_videoFiles = new List<string>
@ -163,76 +172,17 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
Subject.GetImportDecisions(_videoFiles, _series);
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<ParsedEpisodeInfo>(), It.IsAny<bool>()), Times.Exactly(_videoFiles.Count));
Mocker.GetMock<IAugmentingService>()
.Verify(c => c.Augment(It.IsAny<LocalEpisode>(), It.IsAny<bool>()), Times.Exactly(_videoFiles.Count));
ExceptionVerification.ExpectedErrors(3);
}
[Test]
public void should_use_file_quality_if_folder_quality_is_null()
{
GivenSpecifications(_pass1, _pass2, _pass3);
var expectedQuality = QualityParser.ParseQuality(_videoFiles.Single());
var result = Subject.GetImportDecisions(_videoFiles, _series);
result.Single().LocalEpisode.Quality.Should().Be(expectedQuality);
}
[Test]
public void should_use_file_quality_if_file_quality_was_determined_by_name()
{
GivenSpecifications(_pass1, _pass2, _pass3);
var expectedQuality = QualityParser.ParseQuality(_videoFiles.Single());
var result = Subject.GetImportDecisions(_videoFiles, _series, null, new ParsedEpisodeInfo{Quality = new QualityModel(Quality.SDTV)}, true);
result.Single().LocalEpisode.Quality.Should().Be(expectedQuality);
}
[Test]
public void should_use_folder_quality_when_file_quality_was_determined_by_the_extension()
{
GivenSpecifications(_pass1, _pass2, _pass3);
GivenVideoFiles(new string[] { @"C:\Test\Unsorted\The.Office.S03E115.mkv".AsOsAgnostic() });
_localEpisode.Path = _videoFiles.Single();
_localEpisode.Quality.QualitySource = QualitySource.Extension;
_localEpisode.Quality.Quality = Quality.HDTV720p;
var expectedQuality = new QualityModel(Quality.SDTV);
var result = Subject.GetImportDecisions(_videoFiles, _series, null, new ParsedEpisodeInfo { Quality = expectedQuality }, true);
result.Single().LocalEpisode.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() });
_localEpisode.Path = _videoFiles.Single();
_localEpisode.Quality.Quality = Quality.HDTV720p;
var expectedQuality = new QualityModel(Quality.Bluray720p);
var result = Subject.GetImportDecisions(_videoFiles, _series, null, new ParsedEpisodeInfo { Quality = expectedQuality }, true);
result.Single().LocalEpisode.Quality.Should().Be(expectedQuality);
}
[Test]
public void should_not_throw_if_episodes_are_not_found()
{
GivenSpecifications(_pass1);
Mocker.GetMock<IParsingService>()
.Setup(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<ParsedEpisodeInfo>(), It.IsAny<bool>()))
.Returns(new LocalEpisode() { Path = "test", ParsedEpisodeInfo = new ParsedEpisodeInfo { } });
_videoFiles = new List<string>
{
"The.Office.S03E115.DVDRip.XviD-OSiTV",
@ -244,162 +194,18 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
var decisions = Subject.GetImportDecisions(_videoFiles, _series);
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<ParsedEpisodeInfo>(), It.IsAny<bool>()), Times.Exactly(_videoFiles.Count));
Mocker.GetMock<IAugmentingService>()
.Verify(c => c.Augment(It.IsAny<LocalEpisode>(), It.IsAny<bool>()), Times.Exactly(_videoFiles.Count));
decisions.Should().HaveCount(3);
decisions.First().Rejections.Should().NotBeEmpty();
}
[Test]
public void should_not_use_folder_for_full_season()
{
var videoFiles = new[]
{
@"C:\Test\Unsorted\Series.Title.S01\S01E01.mkv".AsOsAgnostic(),
@"C:\Test\Unsorted\Series.Title.S01\S01E02.mkv".AsOsAgnostic(),
@"C:\Test\Unsorted\Series.Title.S01\S01E03.mkv".AsOsAgnostic()
};
GivenSpecifications(_pass1);
GivenVideoFiles(videoFiles);
var folderInfo = Parser.Parser.ParseTitle("Series.Title.S01");
Subject.GetImportDecisions(_videoFiles, _series, null, folderInfo, true);
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), null, true), Times.Exactly(3));
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), It.Is<ParsedEpisodeInfo>(p => p != null), true), Times.Never());
}
[Test]
public void should_not_use_folder_when_it_contains_more_than_one_valid_video_file()
{
var videoFiles = new[]
{
@"C:\Test\Unsorted\Series.Title.S01E01\S01E01.mkv".AsOsAgnostic(),
@"C:\Test\Unsorted\Series.Title.S01E01\1x01.mkv".AsOsAgnostic()
};
GivenSpecifications(_pass1);
GivenVideoFiles(videoFiles);
var folderInfo = Parser.Parser.ParseTitle("Series.Title.S01E01");
Subject.GetImportDecisions(_videoFiles, _series, null, folderInfo, true);
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), null, true), Times.Exactly(2));
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), It.Is<ParsedEpisodeInfo>(p => p != null), true), Times.Never());
}
[Test]
public void should_use_folder_when_only_one_video_file()
{
var videoFiles = new[]
{
@"C:\Test\Unsorted\Series.Title.S01E01\S01E01.mkv".AsOsAgnostic()
};
GivenSpecifications(_pass1);
GivenVideoFiles(videoFiles);
var folderInfo = Parser.Parser.ParseTitle("Series.Title.S01E01");
Subject.GetImportDecisions(_videoFiles, _series, null, folderInfo, true);
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<ParsedEpisodeInfo>(), true), Times.Exactly(1));
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), null, true), Times.Never());
}
[Test]
public void should_use_folder_when_only_one_video_file_and_a_sample()
{
var videoFiles = new[]
{
@"C:\Test\Unsorted\Series.Title.S01E01\S01E01.mkv".AsOsAgnostic(),
@"C:\Test\Unsorted\Series.Title.S01E01\S01E01.sample.mkv".AsOsAgnostic()
};
GivenSpecifications(_pass1);
GivenVideoFiles(videoFiles.ToList());
Mocker.GetMock<IDetectSample>()
.Setup(s => s.IsSample(_series, It.IsAny<string>(), It.IsAny<bool>()))
.Returns((Series s, string path, bool special) =>
{
if (path.Contains("sample"))
{
return DetectSampleResult.Sample;
}
return DetectSampleResult.NotSample;
});
var folderInfo = Parser.Parser.ParseTitle("Series.Title.S01E01");
Subject.GetImportDecisions(_videoFiles, _series, null, folderInfo, true);
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<ParsedEpisodeInfo>(), true), Times.Exactly(2));
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), null, true), Times.Never());
}
[Test]
public void should_not_use_folder_name_if_file_name_is_scene_name()
{
var videoFiles = new[]
{
@"C:\Test\Unsorted\Series.Title.S01E01.720p.HDTV-LOL\Series.Title.S01E01.720p.HDTV-LOL.mkv".AsOsAgnostic()
};
GivenSpecifications(_pass1);
GivenVideoFiles(videoFiles);
var folderInfo = Parser.Parser.ParseTitle("Series.Title.S01E01.720p.HDTV-LOL");
Subject.GetImportDecisions(_videoFiles, _series, null, folderInfo, true);
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), null, true), Times.Exactly(1));
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), It.Is<ParsedEpisodeInfo>(p => p != null), true), Times.Never());
}
[Test]
public void should_not_use_folder_quality_when_it_is_unknown()
{
GivenSpecifications(_pass1, _pass2, _pass3);
_series.Profile = new Profile
{
Items = Qualities.QualityFixture.GetDefaultQualities(Quality.DVD, Quality.Unknown)
};
var folderQuality = new QualityModel(Quality.Unknown);
var result = Subject.GetImportDecisions(_videoFiles, _series, null, new ParsedEpisodeInfo { Quality = folderQuality}, true);
result.Single().LocalEpisode.Quality.Should().Be(_quality);
}
[Test]
public void should_return_a_decision_when_exception_is_caught()
{
Mocker.GetMock<IParsingService>()
.Setup(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<ParsedEpisodeInfo>(), It.IsAny<bool>()))
Mocker.GetMock<IAugmentingService>()
.Setup(c => c.Augment(It.IsAny<LocalEpisode>(), It.IsAny<bool>()))
.Throws<TestException>();
_videoFiles = new List<string>

@ -1,4 +1,4 @@
using FizzWare.NBuilder;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
@ -22,7 +22,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
Path = @"C:\Test\30 Rock\30.rock.s01e01.avi".AsOsAgnostic(),
Size = 100,
Series = Builder<Series>.CreateNew().Build(),
ParsedEpisodeInfo = new ParsedEpisodeInfo
FileEpisodeInfo = new ParsedEpisodeInfo
{
FullSeason = false
}
@ -32,7 +32,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
[Test]
public void should_return_false_when_file_contains_the_full_season()
{
_localEpisode.ParsedEpisodeInfo.FullSeason = true;
_localEpisode.FileEpisodeInfo.FullSeason = true;
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeFalse();
}

@ -1,4 +1,4 @@
using FizzWare.NBuilder;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
@ -18,7 +18,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
{
_localEpisode = Builder<LocalEpisode>.CreateNew()
.With(l => l.Path = @"C:\Test\Unsorted\Series.Title.S01E01.720p.HDTV-Sonarr\S01E05.mkv".AsOsAgnostic())
.With(l => l.ParsedEpisodeInfo =
.With(l => l.FileEpisodeInfo =
Builder<ParsedEpisodeInfo>.CreateNew()
.With(p => p.EpisodeNumbers = new[] {5})
.With(p => p.FullSeason = false)
@ -53,7 +53,8 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
[Test]
public void should_be_accepted_if_file_and_folder_have_the_same_episode()
{
_localEpisode.ParsedEpisodeInfo.EpisodeNumbers = new[] { 1 };
_localEpisode.FileEpisodeInfo.EpisodeNumbers = new[] { 1 };
_localEpisode.FolderEpisodeInfo.EpisodeNumbers = new[] { 1 };
_localEpisode.Path = @"C:\Test\Unsorted\Series.Title.S01E01.720p.HDTV-Sonarr\S01E01.mkv".AsOsAgnostic();
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue();
}
@ -61,7 +62,8 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
[Test]
public void should_be_accepted_if_file_is_one_episode_in_folder()
{
_localEpisode.ParsedEpisodeInfo.EpisodeNumbers = new[] { 1 };
_localEpisode.FileEpisodeInfo.EpisodeNumbers = new[] { 1 };
_localEpisode.FolderEpisodeInfo.EpisodeNumbers = new[] { 1 };
_localEpisode.Path = @"C:\Test\Unsorted\Series.Title.S01E01E02.720p.HDTV-Sonarr\S01E01.mkv".AsOsAgnostic();
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue();
}
@ -76,9 +78,10 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
[Test]
public void should_be_rejected_if_file_and_folder_do_not_have_same_episodes()
{
_localEpisode.ParsedEpisodeInfo.EpisodeNumbers = new[] { 5, 6 };
_localEpisode.FileEpisodeInfo.EpisodeNumbers = new[] { 5, 6 };
_localEpisode.FolderEpisodeInfo.EpisodeNumbers = new[] { 1, 2 };
_localEpisode.Path = @"C:\Test\Unsorted\Series.Title.S01E01E02.720p.HDTV-Sonarr\S01E05E06.mkv".AsOsAgnostic();
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeFalse();
}
}
}
}

@ -58,10 +58,7 @@ namespace NzbDrone.Core.Test.MediaFiles
Episodes = new List<Episode> { episode },
Path = Path.Combine(series.Path, "30 Rock - S01E01 - Pilot.avi"),
Quality = new QualityModel(Quality.Bluray720p),
ParsedEpisodeInfo = new ParsedEpisodeInfo
{
ReleaseGroup = "DRONE"
}
ReleaseGroup = "DRONE"
}));
}

@ -301,6 +301,9 @@
<Compile Include="MediaFiles\DownloadedEpisodesCommandServiceFixture.cs" />
<Compile Include="MediaFiles\DownloadedEpisodesImportServiceFixture.cs" />
<Compile Include="MediaFiles\EpisodeFileMovingServiceTests\MoveEpisodeFileFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\AggregateEpisodesFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\AggregateQualityFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\Augmenters\Quality\AugmentQualityFromMediaInfoFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\ImportDecisionMakerFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\DetectSampleFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\EpisodeTitleSpecificationFixture.cs" />

@ -70,7 +70,7 @@ namespace NzbDrone.Core.Test.ParserTests
public void should_parse_quality_from_extension(string title)
{
Parser.Parser.ParseTitle(title).Quality.Quality.Should().NotBe(Quality.Unknown);
Parser.Parser.ParseTitle(title).Quality.QualitySource.Should().Be(QualitySource.Extension);
Parser.Parser.ParseTitle(title).Quality.QualityDetectionSource.Should().Be(QualityDetectionSource.Extension);
}

@ -283,7 +283,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")]
@ -292,7 +292,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);
}
private void ParseAndVerifyQuality(string title, Quality quality, bool proper)

@ -6,7 +6,9 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.Extras.Metadata.Files;
using NzbDrone.Core.Extras.Subtitles;
using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Extras.Metadata
@ -14,18 +16,18 @@ namespace NzbDrone.Core.Extras.Metadata
public class ExistingMetadataImporter : ImportExistingExtraFilesBase<MetadataFile>
{
private readonly IExtraFileService<MetadataFile> _metadataFileService;
private readonly IParsingService _parsingService;
private readonly IAugmentingService _augmentingService;
private readonly Logger _logger;
private readonly List<IMetadata> _consumers;
public ExistingMetadataImporter(IExtraFileService<MetadataFile> metadataFileService,
IEnumerable<IMetadata> consumers,
IParsingService parsingService,
IAugmentingService augmentingService,
Logger logger)
: base(metadataFileService)
{
_metadataFileService = metadataFileService;
_parsingService = parsingService;
_augmentingService = augmentingService;
_logger = logger;
_consumers = consumers.ToList();
}
@ -60,9 +62,18 @@ namespace NzbDrone.Core.Extras.Metadata
if (metadata.Type == MetadataType.EpisodeImage ||
metadata.Type == MetadataType.EpisodeMetadata)
{
var localEpisode = _parsingService.GetLocalEpisode(possibleMetadataFile, series);
var localEpisode = new LocalEpisode
{
FileEpisodeInfo = Parser.Parser.ParsePath(possibleMetadataFile),
Series = series,
Path = possibleMetadataFile
};
if (localEpisode == null)
try
{
_augmentingService.Augment(localEpisode, false);
}
catch (AugmentingFailedException ex)
{
_logger.Debug("Unable to parse extra file: {0}", possibleMetadataFile);
continue;

@ -1,10 +1,13 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Extras.Others
@ -12,16 +15,16 @@ namespace NzbDrone.Core.Extras.Others
public class ExistingOtherExtraImporter : ImportExistingExtraFilesBase<OtherExtraFile>
{
private readonly IExtraFileService<OtherExtraFile> _otherExtraFileService;
private readonly IParsingService _parsingService;
private readonly IAugmentingService _augmentingService;
private readonly Logger _logger;
public ExistingOtherExtraImporter(IExtraFileService<OtherExtraFile> otherExtraFileService,
IParsingService parsingService,
IAugmentingService augmentingService,
Logger logger)
: base(otherExtraFileService)
{
_otherExtraFileService = otherExtraFileService;
_parsingService = parsingService;
_augmentingService = augmentingService;
_logger = logger;
}
@ -44,9 +47,18 @@ namespace NzbDrone.Core.Extras.Others
continue;
}
var localEpisode = _parsingService.GetLocalEpisode(possibleExtraFile, series);
var localEpisode = new LocalEpisode
{
FileEpisodeInfo = Parser.Parser.ParsePath(possibleExtraFile),
Series = series,
Path = possibleExtraFile
};
if (localEpisode == null)
try
{
_augmentingService.Augment(localEpisode, false);
}
catch (AugmentingFailedException ex)
{
_logger.Debug("Unable to parse extra file: {0}", possibleExtraFile);
continue;

@ -4,7 +4,9 @@ using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Extras.Subtitles
@ -12,16 +14,16 @@ namespace NzbDrone.Core.Extras.Subtitles
public class ExistingSubtitleImporter : ImportExistingExtraFilesBase<SubtitleFile>
{
private readonly IExtraFileService<SubtitleFile> _subtitleFileService;
private readonly IParsingService _parsingService;
private readonly IAugmentingService _augmentingService;
private readonly Logger _logger;
public ExistingSubtitleImporter(IExtraFileService<SubtitleFile> subtitleFileService,
IParsingService parsingService,
IAugmentingService augmentingService,
Logger logger)
: base (subtitleFileService)
{
_subtitleFileService = subtitleFileService;
_parsingService = parsingService;
_augmentingService = augmentingService;
_logger = logger;
}
@ -40,11 +42,20 @@ namespace NzbDrone.Core.Extras.Subtitles
if (SubtitleFileExtensions.Extensions.Contains(extension))
{
var localEpisode = _parsingService.GetLocalEpisode(possibleSubtitleFile, series);
var localEpisode = new LocalEpisode
{
FileEpisodeInfo = Parser.Parser.ParsePath(possibleSubtitleFile),
Series = series,
Path = possibleSubtitleFile
};
if (localEpisode == null)
try
{
_augmentingService.Augment(localEpisode, false);
}
catch (AugmentingFailedException ex)
{
_logger.Debug("Unable to parse subtitle file: {0}", possibleSubtitleFile);
_logger.Debug("Unable to parse extra file: {0}", possibleSubtitleFile);
continue;
}

@ -156,14 +156,7 @@ namespace NzbDrone.Core.MediaFiles
return new List<ImportResult>();
}
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name);
if (folderInfo != null)
{
_logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality);
}
var videoFiles = _diskScanService.FilterFiles(directoryInfo.FullName, _diskScanService.GetVideoFiles(directoryInfo.FullName));
if (downloadClientItem == null)

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

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.IO;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation
{
public interface IAugmentingService
{
LocalEpisode Augment(LocalEpisode localEpisode, bool otherFiles);
}
public class AugmentingService : IAugmentingService
{
private readonly IEnumerable<IAggregateLocalEpisode> _augmenters;
private readonly IDiskProvider _diskProvider;
private readonly IVideoFileInfoReader _videoFileInfoReader;
private readonly Logger _logger;
public AugmentingService(IEnumerable<IAggregateLocalEpisode> augmenters,
IDiskProvider diskProvider,
IVideoFileInfoReader videoFileInfoReader,
Logger logger)
{
_augmenters = augmenters;
_diskProvider = diskProvider;
_videoFileInfoReader = videoFileInfoReader;
_logger = logger;
}
public LocalEpisode Augment(LocalEpisode localEpisode, bool otherFiles)
{
if (localEpisode.DownloadClientEpisodeInfo == null &&
localEpisode.FolderEpisodeInfo == null &&
localEpisode.FileEpisodeInfo == null)
{
if (MediaFileExtensions.Extensions.Contains(Path.GetExtension(localEpisode.Path)))
{
throw new AugmentingFailedException("Unable to parse episode info from path: {0}", localEpisode.Path);
}
}
localEpisode.Size = _diskProvider.GetFileSize(localEpisode.Path);
localEpisode.MediaInfo = _videoFileInfoReader.GetMediaInfo(localEpisode.Path);
foreach (var augmenter in _augmenters)
{
try
{
augmenter.Aggregate(localEpisode, otherFiles);
}
catch (Exception ex)
{
_logger.Warn(ex, ex.Message);
}
}
return localEpisode;
}
}
}

@ -0,0 +1,54 @@
using System.IO;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators
{
public class AggregateEpisodes : IAggregateLocalEpisode
{
private readonly IParsingService _parsingService;
public AggregateEpisodes(IParsingService parsingService)
{
_parsingService = parsingService;
}
public LocalEpisode Aggregate(LocalEpisode localEpisode, bool otherFiles)
{
var bestEpisodeInfoForEpisodes = GetBestEpisodeInfo(localEpisode, otherFiles);
localEpisode.Episodes = _parsingService.GetEpisodes(bestEpisodeInfoForEpisodes, localEpisode.Series, localEpisode.SceneSource);
return localEpisode;
}
private ParsedEpisodeInfo GetBestEpisodeInfo(LocalEpisode localEpisode, bool otherFiles)
{
var parsedEpisodeInfo = localEpisode.FileEpisodeInfo;
var downloadClientEpisodeInfo = localEpisode.DownloadClientEpisodeInfo;
var folderEpisodeInfo = localEpisode.FolderEpisodeInfo;
if (!otherFiles && !SceneChecker.IsSceneTitle(Path.GetFileNameWithoutExtension(localEpisode.Path)))
{
if (downloadClientEpisodeInfo != null && !downloadClientEpisodeInfo.FullSeason)
{
parsedEpisodeInfo = localEpisode.DownloadClientEpisodeInfo;
}
else if (folderEpisodeInfo != null && !folderEpisodeInfo.FullSeason)
{
parsedEpisodeInfo = localEpisode.FolderEpisodeInfo;
}
}
if (parsedEpisodeInfo == null || parsedEpisodeInfo.IsPossibleSpecialEpisode)
{
var title = Path.GetFileNameWithoutExtension(localEpisode.Path);
var specialEpisodeInfo = _parsingService.ParseSpecialEpisodeTitle(parsedEpisodeInfo, title, localEpisode.Series);
return specialEpisodeInfo;
}
return parsedEpisodeInfo;
}
}
}

@ -0,0 +1,78 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators.Augmenters.Quality;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators
{
public class AggregateQuality : IAggregateLocalEpisode
{
private readonly IEnumerable<IAugmentQuality> _augmentQualities;
private readonly Logger _logger;
public AggregateQuality(IEnumerable<IAugmentQuality> augmentQualities,
Logger logger)
{
_augmentQualities = augmentQualities;
_logger = logger;
}
public LocalEpisode Aggregate(LocalEpisode localEpisode, bool otherFiles)
{
var augmentedQualities = _augmentQualities.Select(a => a.AugmentQuality(localEpisode))
.Where(a => a != null)
.OrderBy(a => a.SourceConfidence);
var source = QualitySource.Unknown;
var sourceConfidence = Confidence.Default;
var resolution = 0;
var resolutionConfidence = Confidence.Default;
var revison = new Revision();
foreach (var augmentedQuality in augmentedQualities)
{
if (augmentedQuality.Source > source ||
augmentedQuality.SourceConfidence > sourceConfidence && augmentedQuality.Source != QualitySource.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;
}
}
var quality = new QualityModel(Quality.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);
localEpisode.Quality = quality;
return localEpisode;
}
}
}

@ -0,0 +1,27 @@
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators
{
public class AggregateReleaseGroup : IAggregateLocalEpisode
{
public LocalEpisode Aggregate(LocalEpisode localEpisode, bool otherFiles)
{
var releaseGroup = localEpisode.DownloadClientEpisodeInfo?.ReleaseGroup;
if (releaseGroup.IsNullOrWhiteSpace())
{
releaseGroup = localEpisode.FolderEpisodeInfo?.ReleaseGroup;
}
if (releaseGroup.IsNullOrWhiteSpace())
{
releaseGroup = localEpisode.FileEpisodeInfo?.ReleaseGroup;
}
localEpisode.ReleaseGroup = releaseGroup;
return localEpisode;
}
}
}

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

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

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

@ -0,0 +1,39 @@
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators.Augmenters.Quality
{
public class AugmentQualityFromMediaInfo : IAugmentQuality
{
public AugmentQualityResult AugmentQuality(LocalEpisode localEpisode)
{
if (localEpisode.MediaInfo == null)
{
return null;
}
var width = localEpisode.MediaInfo.Width;
if (width >= 3200)
{
return AugmentQualityResult.ResolutionOnly(2160, Confidence.MediaInfo);
}
if (width >= 1800)
{
return AugmentQualityResult.ResolutionOnly(1080, Confidence.MediaInfo);
}
if (width >= 1200)
{
return AugmentQualityResult.ResolutionOnly(720, Confidence.MediaInfo);
}
if (width > 0)
{
return AugmentQualityResult.ResolutionOnly(480, Confidence.MediaInfo);
}
return null;
}
}
}

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

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

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

@ -0,0 +1,9 @@
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators
{
public interface IAggregateLocalEpisode
{
LocalEpisode Aggregate(LocalEpisode localEpisode, bool otherFiles);
}
}

@ -44,6 +44,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
return DetectSampleResult.NotSample;
}
// TODO: Use MediaInfo from the import process, no need to re-process the file again here
var runTime = _videoFileInfoReader.GetRunTime(path);
if (!runTime.HasValue)

@ -83,7 +83,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
episodeFile.MediaInfo = localEpisode.MediaInfo;
episodeFile.SeasonNumber = localEpisode.SeasonNumber;
episodeFile.Episodes = localEpisode.Episodes;
episodeFile.ReleaseGroup = localEpisode.ParsedEpisodeInfo.ReleaseGroup;
episodeFile.ReleaseGroup = localEpisode.ReleaseGroup;
bool copyOnly;
switch (importMode)

@ -7,12 +7,9 @@ using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser;
using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.MediaFiles.MediaInfo;
namespace NzbDrone.Core.MediaFiles.EpisodeImport
{
@ -25,26 +22,23 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
public class ImportDecisionMaker : IMakeImportDecision
{
private readonly IEnumerable<IImportDecisionEngineSpecification> _specifications;
private readonly IParsingService _parsingService;
private readonly IMediaFileService _mediaFileService;
private readonly IAugmentingService _augmentingService;
private readonly IDiskProvider _diskProvider;
private readonly IVideoFileInfoReader _videoFileInfoReader;
private readonly IDetectSample _detectSample;
private readonly Logger _logger;
public ImportDecisionMaker(IEnumerable<IImportDecisionEngineSpecification> specifications,
IParsingService parsingService,
IMediaFileService mediaFileService,
IAugmentingService augmentingService,
IDiskProvider diskProvider,
IVideoFileInfoReader videoFileInfoReader,
IDetectSample detectSample,
Logger logger)
{
_specifications = specifications;
_parsingService = parsingService;
_mediaFileService = mediaFileService;
_augmentingService = augmentingService;
_diskProvider = diskProvider;
_videoFileInfoReader = videoFileInfoReader;
_detectSample = detectSample;
_logger = logger;
}
@ -60,78 +54,81 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
_logger.Debug("Analyzing {0}/{1} files.", newFiles.Count, videoFiles.Count());
var shouldUseFolderName = ShouldUseFolderName(videoFiles, series, folderInfo);
ParsedEpisodeInfo downloadClientItemInfo = null;
if (downloadClientItem != null)
{
downloadClientItemInfo = Parser.Parser.ParseTitle(downloadClientItem.Title);
}
var nonSampleVideoFileCount = GetNonSampleVideoFileCount(newFiles, series, downloadClientItemInfo, folderInfo);
var decisions = new List<ImportDecision>();
foreach (var file in newFiles)
{
decisions.AddIfNotNull(GetDecision(file, series, downloadClientItem, folderInfo, sceneSource, shouldUseFolderName));
var localEpisode = new LocalEpisode
{
Series = series,
DownloadClientEpisodeInfo = downloadClientItemInfo,
FolderEpisodeInfo = folderInfo,
Path = file,
SceneSource = sceneSource
};
decisions.AddIfNotNull(GetDecision(localEpisode, downloadClientItem, nonSampleVideoFileCount > 1));
}
return decisions;
}
private ImportDecision GetDecision(string file, Series series, DownloadClientItem downloadClientItem, ParsedEpisodeInfo folderInfo, bool sceneSource, bool shouldUseFolderName)
private ImportDecision GetDecision(LocalEpisode localEpisode, DownloadClientItem downloadClientItem, bool otherFiles)
{
ImportDecision decision = null;
var fileEpisodeInfo = Parser.Parser.ParsePath(localEpisode.Path);
localEpisode.FileEpisodeInfo = fileEpisodeInfo;
localEpisode.Size = _diskProvider.GetFileSize(localEpisode.Path);
try
{
var localEpisode = _parsingService.GetLocalEpisode(file, series, shouldUseFolderName ? folderInfo : null, sceneSource);
_augmentingService.Augment(localEpisode, otherFiles);
if (localEpisode != null)
if (localEpisode.Episodes.Empty())
{
localEpisode.Quality = GetQuality(folderInfo, localEpisode.Quality, series);
localEpisode.Size = _diskProvider.GetFileSize(file);
_logger.Debug("Size: {0}", localEpisode.Size);
//TODO: make it so media info doesn't ruin the import process of a new series
if (sceneSource)
if (IsPartialSeason(localEpisode))
{
localEpisode.MediaInfo = _videoFileInfoReader.GetMediaInfo(file);
decision = new ImportDecision(localEpisode, new Rejection("Partial season packs are not supported"));
}
if (localEpisode.Episodes.Empty())
else if (IsSeasonExtra(localEpisode))
{
if (localEpisode.ParsedEpisodeInfo.IsPartialSeason)
{
decision = new ImportDecision(localEpisode, new Rejection("Partial season packs are not supported"));
}
else if (localEpisode.ParsedEpisodeInfo.IsSeasonExtra)
{
decision = new ImportDecision(localEpisode, new Rejection("Extras are not supported"));
}
else
{
decision = new ImportDecision(localEpisode, new Rejection("Invalid season or episode"));
}
decision = new ImportDecision(localEpisode, new Rejection("Extras are not supported"));
}
else
{
decision = GetDecision(localEpisode, downloadClientItem);
decision = new ImportDecision(localEpisode, new Rejection("Invalid season or episode"));
}
}
else
{
localEpisode = new LocalEpisode();
localEpisode.Path = file;
decision = new ImportDecision(localEpisode, new Rejection("Unable to parse file"));
decision = GetDecision(localEpisode, downloadClientItem);
}
}
catch (Exception e)
catch (AugmentingFailedException)
{
_logger.Error(e, "Couldn't import file. {0}", file);
decision = new ImportDecision(localEpisode, new Rejection("Unable to parse file"));
}
catch (Exception ex)
{
_logger.Error(ex, "Couldn't import file. {0}", localEpisode.Path);
var localEpisode = new LocalEpisode { Path = file };
decision = new ImportDecision(localEpisode, new Rejection("Unexpected error processing file"));
}
if (decision == null)
{
_logger.Error("Unable to make a decision on {0}", file);
_logger.Error("Unable to make a decision on {0}", localEpisode.Path);
}
else if (decision.Rejections.Any())
{
@ -175,65 +172,66 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
return null;
}
private bool ShouldUseFolderName(List<string> videoFiles, Series series, ParsedEpisodeInfo folderInfo)
private int GetNonSampleVideoFileCount(List<string> videoFiles, Series series, ParsedEpisodeInfo downloadClientItemInfo, ParsedEpisodeInfo folderInfo)
{
if (folderInfo == null)
{
return false;
}
if (folderInfo.FullSeason)
{
return false;
}
var isPossibleSpecialEpisode = downloadClientItemInfo?.IsPossibleSpecialEpisode ?? false;
// If we might already have a special, don't try to get it from the folder info.
isPossibleSpecialEpisode = isPossibleSpecialEpisode || (folderInfo?.IsPossibleSpecialEpisode ?? false);
return videoFiles.Count(file =>
{
var sample = _detectSample.IsSample(series, file, folderInfo.IsPossibleSpecialEpisode);
var sample = _detectSample.IsSample(series, file, isPossibleSpecialEpisode);
if (sample == DetectSampleResult.Sample)
{
return false;
}
if (SceneChecker.IsSceneTitle(Path.GetFileName(file)))
{
return false;
}
return true;
}) == 1;
});
}
private QualityModel GetQuality(ParsedEpisodeInfo folderInfo, QualityModel fileQuality, Series series)
private bool IsPartialSeason(LocalEpisode localEpisode)
{
if (UseFolderQuality(folderInfo, fileQuality, series))
var downloadClientEpisodeInfo = localEpisode.DownloadClientEpisodeInfo;
var folderEpisodeInfo = localEpisode.FolderEpisodeInfo;
var fileEpisodeInfo = localEpisode.FileEpisodeInfo;
if (downloadClientEpisodeInfo != null && downloadClientEpisodeInfo.IsPartialSeason)
{
_logger.Debug("Using quality from folder: {0}", folderInfo.Quality);
return folderInfo.Quality;
return true;
}
return fileQuality;
}
if (folderEpisodeInfo != null && folderEpisodeInfo.IsPartialSeason)
{
return true;
}
private bool UseFolderQuality(ParsedEpisodeInfo folderInfo, QualityModel fileQuality, Series series)
{
if (folderInfo == null)
if (fileEpisodeInfo != null && fileEpisodeInfo.IsPartialSeason)
{
return false;
return true;
}
if (folderInfo.Quality.Quality == Quality.Unknown)
return false;
}
private bool IsSeasonExtra(LocalEpisode localEpisode)
{
var downloadClientEpisodeInfo = localEpisode.DownloadClientEpisodeInfo;
var folderEpisodeInfo = localEpisode.FolderEpisodeInfo;
var fileEpisodeInfo = localEpisode.FileEpisodeInfo;
if (downloadClientEpisodeInfo != null && downloadClientEpisodeInfo.IsSeasonExtra)
{
return false;
return true;
}
if (fileQuality.QualitySource == QualitySource.Extension)
if (folderEpisodeInfo != null && folderEpisodeInfo.IsSeasonExtra)
{
return true;
}
if (new QualityModelComparer(series.Profile).Compare(folderInfo.Quality, fileQuality) > 0)
if (fileEpisodeInfo != null && fileEpisodeInfo.IsSeasonExtra)
{
return true;
}

@ -245,7 +245,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
var file = message.Files[i];
var series = _seriesService.GetSeries(file.SeriesId);
var episodes = _episodeService.GetEpisodes(file.EpisodeIds);
var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo();
var fileEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo();
var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path);
var existingFile = series.Path.IsParentPath(file.Path);
@ -254,7 +254,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
ExistingFile = false,
Episodes = episodes,
MediaInfo = mediaInfo,
ParsedEpisodeInfo = parsedEpisodeInfo,
FileEpisodeInfo = fileEpisodeInfo,
Path = file.Path,
Quality = file.Quality,
Series = series,

@ -1,4 +1,4 @@
using NLog;
using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model;
@ -16,7 +16,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
{
if (localEpisode.ParsedEpisodeInfo.FullSeason)
if (localEpisode.FileEpisodeInfo.FullSeason)
{
_logger.Debug("Single episode file detected as containing all episodes in the season");
return Decision.Reject("Single episode file contains all episodes in seasons");

@ -1,4 +1,4 @@
using System.IO;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Core.DecisionEngine;
@ -32,7 +32,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
return Decision.Accept();
}
var folderInfo = Parser.Parser.ParseTitle(dirInfo.Name);
var folderInfo = localEpisode.FileEpisodeInfo;
if (folderInfo != null && folderInfo.IsPossibleSceneSeasonSpecial)
{
@ -54,7 +54,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
return Decision.Accept();
}
var unexpected = localEpisode.ParsedEpisodeInfo.EpisodeNumbers.Where(f => !folderInfo.EpisodeNumbers.Contains(f)).ToList();
var unexpected = localEpisode.FileEpisodeInfo.EpisodeNumbers.Where(f => !folderInfo.EpisodeNumbers.Contains(f)).ToList();
if (unexpected.Any())
{

@ -1,4 +1,4 @@
using System;
using System;
using System.Globalization;
using System.IO;
using NLog;
@ -36,6 +36,8 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
MediaInfo mediaInfo = null;
// TODO: Cache media info by path, mtime and length so we don't need to read files multiple times
try
{
mediaInfo = new MediaInfo();

@ -763,7 +763,20 @@
<Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" />
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
<Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\AggregateEpisodes.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\AggregateQuality.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\AggregateReleaseGroup.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\Augmenters\Quality\AugmentQualityFromFileName.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\Augmenters\Quality\AugmentQualityFromFolder.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\Augmenters\Quality\AugmentQualityFromDownloadClientItem.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\Augmenters\Quality\AugmentQualityFromMediaInfo.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\Augmenters\Quality\AugmentQualityResult.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\Augmenters\Quality\Confidence.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\Augmenters\Quality\IAugmentQuality.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\IAggregateLocalEpisode.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\AggregationService.cs" />
<Compile Include="MediaFiles\EpisodeImport\DetectSampleResult.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\AggregationFailedException.cs" />
<Compile Include="MediaFiles\EpisodeImport\ImportMode.cs" />
<Compile Include="MediaFiles\Commands\RenameFilesCommand.cs" />
<Compile Include="MediaFiles\Commands\RenameSeriesCommand.cs" />
@ -953,6 +966,7 @@
<Compile Include="Profiles\Delay\DelayProfileTagInUseValidator.cs" />
<Compile Include="Profiles\ProfileRepository.cs" />
<Compile Include="ProgressMessaging\ProgressMessageContext.cs" />
<Compile Include="Qualities\QualityDetectionSource.cs" />
<Compile Include="Qualities\QualitySource.cs" />
<Compile Include="Qualities\Revision.cs" />
<Compile Include="RemotePathMappings\RemotePathMapping.cs" />

@ -16,12 +16,16 @@ namespace NzbDrone.Core.Parser.Model
public string Path { get; set; }
public long Size { get; set; }
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; }
public ParsedEpisodeInfo FileEpisodeInfo { get; set; }
public ParsedEpisodeInfo DownloadClientEpisodeInfo { get; set; }
public ParsedEpisodeInfo FolderEpisodeInfo { get; set; }
public Series Series { get; set; }
public List<Episode> Episodes { get; set; }
public QualityModel Quality { get; set; }
public MediaInfoModel MediaInfo { get; set; }
public bool ExistingFile { get; set; }
public bool SceneSource { get; set; }
public string ReleaseGroup { get; set; }
public int SeasonNumber
{

@ -1,11 +1,9 @@
using System.Collections.Generic;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
@ -13,13 +11,12 @@ namespace NzbDrone.Core.Parser
{
public interface IParsingService
{
LocalEpisode GetLocalEpisode(string filename, Series series);
LocalEpisode GetLocalEpisode(string filename, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource);
Series GetSeries(string title);
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds);
List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null);
ParsedEpisodeInfo ParseSpecialEpisodeTitle(ParsedEpisodeInfo parsedEpisodeInfo, string releaseTitle, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
ParsedEpisodeInfo ParseSpecialEpisodeTitle(ParsedEpisodeInfo parsedEpisodeInfo, string releaseTitle, Series series);
}
public class ParsingService : IParsingService
@ -40,60 +37,6 @@ namespace NzbDrone.Core.Parser
_logger = logger;
}
public LocalEpisode GetLocalEpisode(string filename, Series series)
{
return GetLocalEpisode(filename, series, null, false);
}
public LocalEpisode GetLocalEpisode(string filename, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource)
{
ParsedEpisodeInfo parsedEpisodeInfo;
if (folderInfo != null)
{
parsedEpisodeInfo = folderInfo.JsonClone();
parsedEpisodeInfo.Quality = QualityParser.ParseQuality(Path.GetFileName(filename));
}
else
{
parsedEpisodeInfo = Parser.ParsePath(filename);
}
if (parsedEpisodeInfo == null || parsedEpisodeInfo.IsPossibleSpecialEpisode)
{
var title = Path.GetFileNameWithoutExtension(filename);
var specialEpisodeInfo = ParseSpecialEpisodeTitle(parsedEpisodeInfo, title, series);
if (specialEpisodeInfo != null)
{
parsedEpisodeInfo = specialEpisodeInfo;
}
}
if (parsedEpisodeInfo == null)
{
if (MediaFileExtensions.Extensions.Contains(Path.GetExtension(filename)))
{
_logger.Warn("Unable to parse episode info from path {0}", filename);
}
return null;
}
var episodes = GetEpisodes(parsedEpisodeInfo, series, sceneSource);
return new LocalEpisode
{
Series = series,
Quality = parsedEpisodeInfo.Quality,
Episodes = episodes,
Path = filename,
ParsedEpisodeInfo = parsedEpisodeInfo,
ExistingFile = series.Path.IsParentPath(filename)
};
}
public Series GetSeries(string title)
{
var parsedEpisodeInfo = Parser.ParseTitle(title);
@ -225,7 +168,7 @@ namespace NzbDrone.Core.Parser
return ParseSpecialEpisodeTitle(parsedEpisodeInfo, releaseTitle, series);
}
private ParsedEpisodeInfo ParseSpecialEpisodeTitle(ParsedEpisodeInfo parsedEpisodeInfo, string releaseTitle, Series series)
public ParsedEpisodeInfo ParseSpecialEpisodeTitle(ParsedEpisodeInfo parsedEpisodeInfo, string releaseTitle, Series series)
{
// SxxE00 episodes are sometimes mapped via TheXEM, don't use episode title parsing in that case.
if (parsedEpisodeInfo != null && parsedEpisodeInfo.IsPossibleSceneSeasonSpecial && series.UseSceneNumbering)

@ -66,7 +66,7 @@ namespace NzbDrone.Core.Parser
try
{
result.Quality = MediaFileExtensions.GetQualityForExtension(Path.GetExtension(name));
result.QualitySource = QualitySource.Extension;
result.QualityDetectionSource = QualityDetectionSource.Extension;
}
catch (ArgumentException)
{

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Datastore;
@ -9,15 +9,19 @@ namespace NzbDrone.Core.Qualities
{
public int Id { get; set; }
public string Name { get; set; }
public QualitySource Source { get; set; }
public int Resolution { get; set; }
public Quality()
{
}
private Quality(int id, string name)
private Quality(int id, string name, QualitySource source, int resolution)
{
Id = id;
Name = name;
Source = source;
Resolution = resolution;
}
public override string ToString()
@ -55,26 +59,26 @@ namespace NzbDrone.Core.Qualities
return !Equals(left, right);
}
public static Quality Unknown => new Quality(0, "Unknown");
public static Quality SDTV => new Quality(1, "SDTV");
public static Quality DVD => new Quality(2, "DVD");
public static Quality WEBDL1080p => new Quality(3, "WEBDL-1080p");
public static Quality HDTV720p => new Quality(4, "HDTV-720p");
public static Quality WEBDL720p => new Quality(5, "WEBDL-720p");
public static Quality Bluray720p => new Quality(6, "Bluray-720p");
public static Quality Bluray1080p => new Quality(7, "Bluray-1080p");
public static Quality WEBDL480p => new Quality(8, "WEBDL-480p");
public static Quality HDTV1080p => new Quality(9, "HDTV-1080p");
public static Quality RAWHD => new Quality(10, "Raw-HD");
//public static Quality HDTV480p { get { return new Quality(11, "HDTV-480p"); } }
//public static Quality WEBRip480p { get { return new Quality(12, "WEBRip-480p"); } }
//public static Quality Bluray480p { get { return new Quality(13, "Bluray-480p"); } }
//public static Quality WEBRip720p { get { return new Quality(14, "WEBRip-720p"); } }
//public static Quality WEBRip1080p { get { return new Quality(15, "WEBRip-1080p"); } }
public static Quality HDTV2160p => new Quality(16, "HDTV-2160p");
//public static Quality WEBRip2160p { get { return new Quality(17, "WEBRip-2160p"); } }
public static Quality WEBDL2160p => new Quality(18, "WEBDL-2160p");
public static Quality Bluray2160p => new Quality(19, "Bluray-2160p");
public static Quality Unknown => new Quality(0, "Unknown", QualitySource.Unknown, 0);
public static Quality SDTV => new Quality(1, "SDTV", QualitySource.Television, 480);
public static Quality DVD => new Quality(2, "DVD", QualitySource.DVD, 480);
public static Quality WEBDL1080p => new Quality(3, "WEBDL-1080p", QualitySource.Web, 1080);
public static Quality HDTV720p => new Quality(4, "HDTV-720p", QualitySource.Television, 720);
public static Quality WEBDL720p => new Quality(5, "WEBDL-720p", QualitySource.Web, 720);
public static Quality Bluray720p => new Quality(6, "Bluray-720p", QualitySource.Bluray, 720);
public static Quality Bluray1080p => new Quality(7, "Bluray-1080p", QualitySource.Bluray, 1080);
public static Quality WEBDL480p => new Quality(8, "WEBDL-480p", QualitySource.Web, 480);
public static Quality HDTV1080p => new Quality(9, "HDTV-1080p", QualitySource.Television, 1080);
public static Quality RAWHD => new Quality(10, "Raw-HD", QualitySource.TelevisionRaw, 1080);
//public static Quality HDTV480p { get { return new Quality(11, "HDTV-480p", QualitySource.Television, 480); } }
//public static Quality WEBRip480p { get { return new Quality(12, "WEBRip-480p", QualitySource.WebRip, 480); } }
//public static Quality Bluray480p { get { return new Quality(13, "Bluray-480p", QualitySource.Bluray, 480); } }
//public static Quality WEBRip720p { get { return new Quality(14, "WEBRip-720p", QualitySource.WebRip, 720); } }
//public static Quality WEBRip1080p { get { return new Quality(15, "WEBRip-1080p", QualitySource.WebRip, 1080); } }
public static Quality HDTV2160p => new Quality(16, "HDTV-2160p", QualitySource.Television, 2160);
//public static Quality WEBRip2160p { get { return new Quality(17, "WEBRip-2160p", QualitySource.WebRip, 2160); } }
public static Quality WEBDL2160p => new Quality(18, "WEBDL-2160p", QualitySource.Web, 2160);
public static Quality Bluray2160p => new Quality(19, "Bluray-2160p", QualitySource.Bluray, 2160);
static Quality()
{
@ -148,5 +152,10 @@ namespace NzbDrone.Core.Qualities
{
return quality.Id;
}
public static Quality FindBySourceAndResolution(QualitySource source, int resolution)
{
return All.SingleOrDefault(q => q.Source == source && q.Resolution == resolution);
}
}
}
}

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

@ -1,4 +1,4 @@
using System;
using System;
using Newtonsoft.Json;
using NzbDrone.Core.Datastore;
@ -10,8 +10,8 @@ namespace NzbDrone.Core.Qualities
public Revision Revision { get; set; }
[JsonIgnore]
public QualitySource QualitySource { get; set; }
public QualityDetectionSource QualityDetectionSource { get; set; }
public QualityModel()
: this(Quality.Unknown, new Revision())
{

@ -1,9 +1,13 @@
namespace NzbDrone.Core.Qualities
namespace NzbDrone.Core.Qualities
{
public enum QualitySource
{
Name,
Extension,
MediaInfo
Unknown,
Television,
TelevisionRaw,
Web,
WebRip,
DVD,
Bluray
}
}

Loading…
Cancel
Save