MediaInfo added

New: Samples will only be skipped when under 70MB and under 8 minutes in
length
#ND-121 fixed
This commit is contained in:
Mark McDowall 2013-01-20 16:35:42 -08:00
parent eeb16d6d5a
commit e136b458e0
9 changed files with 294 additions and 50 deletions

View File

@ -324,7 +324,7 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
}
[Test]
public void import_new_episode_no_existing_episode_file()
public void should_import_new_episode_no_existing_episode_file()
{
const string fileName = "WEEDS.S03E01E02.DUAL.bluray.x264.AC3.-HELLYWOOD.mkv";
@ -354,48 +354,6 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
Mocker.GetMock<DiskProvider>().Verify(p => p.DeleteFile(It.IsAny<string>()), Times.Never());
}
[Test]
public void should_return_null_if_file_size_is_under_40MB()
{
var series = Builder<Series>
.CreateNew()
.Build();
const string path = @"C:\Test\TV\30.rock.s01e01.pilot.avi";
Mocker.GetMock<MediaFileProvider>()
.Setup(m => m.Exists(path))
.Returns(false);
Mocker.GetMock<DiskProvider>()
.Setup(d => d.GetSize(path))
.Returns(20.Megabytes());
Mocker.Resolve<DiskScanProvider>().ImportFile(series, path).Should().BeNull();
}
private static void VerifyFileImport(EpisodeFile result, AutoMoqer Mocker, Episode fakeEpisode, long size)
{
result.Should().NotBeNull();
result.SeriesId.Should().Be(fakeEpisode.SeriesId);
result.Size.Should().Be(size);
result.DateAdded.Should().HaveDay(DateTime.Now.Day);
Mocker.GetMock<MediaFileProvider>().Verify(p => p.Add(It.IsAny<EpisodeFile>()), Times.Once());
//Get the count of episodes linked
var count = Mocker.GetMock<EpisodeProvider>().Object.GetEpisodesByParseResult(null).Count;
Mocker.GetMock<EpisodeProvider>().Verify(p => p.UpdateEpisode(It.Is<Episode>(e => e.EpisodeFileId == result.EpisodeFileId)), Times.Exactly(count));
}
private static void VerifySkipImport(EpisodeFile result, AutoMoqer Mocker)
{
result.Should().BeNull();
Mocker.GetMock<MediaFileProvider>().Verify(p => p.Add(It.IsAny<EpisodeFile>()), Times.Never());
Mocker.GetMock<EpisodeProvider>().Verify(p => p.UpdateEpisode(It.IsAny<Episode>()), Times.Never());
Mocker.GetMock<DiskProvider>().Verify(p => p.DeleteFile(It.IsAny<string>()), Times.Never());
}
[Test]
public void should_set_parseResult_SceneSource_if_not_in_series_Path()
{
@ -441,5 +399,115 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
Mocker.Verify<EpisodeProvider>(s => s.GetEpisodesByParseResult(It.Is<EpisodeParseResult>(p => p.SceneSource == false)), Times.Once());
}
[Test]
public void should_return_null_if_file_size_is_under_70MB_and_runTime_under_8_minutes()
{
var series = Builder<Series>
.CreateNew()
.Build();
const string path = @"C:\Test\TV\30.rock.s01e01.pilot.avi";
Mocker.GetMock<MediaFileProvider>()
.Setup(m => m.Exists(path))
.Returns(false);
Mocker.GetMock<DiskProvider>()
.Setup(d => d.GetSize(path))
.Returns(20.Megabytes());
Mocker.GetMock<MediaInfoProvider>()
.Setup(s => s.GetRunTime(path))
.Returns(300);
Mocker.Resolve<DiskScanProvider>().ImportFile(series, path).Should().BeNull();
}
[Test]
public void should_import_if_file_size_is_under_70MB_but_runTime_over_8_minutes()
{
var fakeSeries = Builder<Series>.CreateNew().Build();
var fakeEpisode = Builder<Episode>.CreateNew()
.With(e => e.EpisodeFileId = 0)
.With(e => e.EpisodeFile = null)
.Build();
const string path = @"C:\Test\TV\30.rock.s01e01.pilot.avi";
Mocker.GetMock<MediaFileProvider>()
.Setup(m => m.Exists(path))
.Returns(false);
Mocker.GetMock<DiskProvider>()
.Setup(d => d.GetSize(path))
.Returns(20.Megabytes());
Mocker.GetMock<MediaInfoProvider>()
.Setup(s => s.GetRunTime(path))
.Returns(600);
Mocker.GetMock<EpisodeProvider>()
.Setup(e => e.GetEpisodesByParseResult(It.IsAny<EpisodeParseResult>())).Returns(new List<Episode> { fakeEpisode });
var result = Mocker.Resolve<DiskScanProvider>().ImportFile(fakeSeries, path);
VerifyFileImport(result, Mocker, fakeEpisode, 20.Megabytes());
Mocker.GetMock<DiskProvider>().Verify(p => p.DeleteFile(It.IsAny<string>()), Times.Never());
}
[Test]
public void should_import_if_file_size_is_over_70MB_but_runTime_under_8_minutes()
{
With80MBFile();
var fakeSeries = Builder<Series>.CreateNew().Build();
var fakeEpisode = Builder<Episode>.CreateNew()
.With(e => e.EpisodeFileId = 0)
.With(e => e.EpisodeFile = null)
.Build();
const string path = @"C:\Test\TV\30.rock.s01e01.pilot.avi";
Mocker.GetMock<MediaFileProvider>()
.Setup(m => m.Exists(path))
.Returns(false);
Mocker.GetMock<MediaInfoProvider>()
.Setup(s => s.GetRunTime(path))
.Returns(600);
Mocker.GetMock<EpisodeProvider>()
.Setup(e => e.GetEpisodesByParseResult(It.IsAny<EpisodeParseResult>())).Returns(new List<Episode> { fakeEpisode });
var result = Mocker.Resolve<DiskScanProvider>().ImportFile(fakeSeries, path);
VerifyFileImport(result, Mocker, fakeEpisode, SIZE);
Mocker.GetMock<DiskProvider>().Verify(p => p.DeleteFile(It.IsAny<string>()), Times.Never());
}
private static void VerifyFileImport(EpisodeFile result, AutoMoqer Mocker, Episode fakeEpisode, long size)
{
result.Should().NotBeNull();
result.SeriesId.Should().Be(fakeEpisode.SeriesId);
result.Size.Should().Be(size);
result.DateAdded.Should().HaveDay(DateTime.Now.Day);
Mocker.GetMock<MediaFileProvider>().Verify(p => p.Add(It.IsAny<EpisodeFile>()), Times.Once());
//Get the count of episodes linked
var count = Mocker.GetMock<EpisodeProvider>().Object.GetEpisodesByParseResult(null).Count;
Mocker.GetMock<EpisodeProvider>().Verify(p => p.UpdateEpisode(It.Is<Episode>(e => e.EpisodeFileId == result.EpisodeFileId)), Times.Exactly(count));
}
private static void VerifySkipImport(EpisodeFile result, AutoMoqer Mocker)
{
result.Should().BeNull();
Mocker.GetMock<MediaFileProvider>().Verify(p => p.Add(It.IsAny<EpisodeFile>()), Times.Never());
Mocker.GetMock<EpisodeProvider>().Verify(p => p.UpdateEpisode(It.IsAny<Episode>()), Times.Never());
Mocker.GetMock<DiskProvider>().Verify(p => p.DeleteFile(It.IsAny<string>()), Times.Never());
}
}
}

BIN
NzbDrone.Core/MediaInfo.dll Normal file

Binary file not shown.

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Model
{
public class MediaInfoModel
{
public string VideoCodec { get; set; }
public int VideoBitrate { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public string AudioFormat { get; set; }
public int AudioBitrate { get; set; }
public int RunTime { get; set; }
public int AudioStreamCount { get; set; }
public int AudioChannels { get; set; }
public string AudioProfile { get; set; }
public decimal VideoFps { get; set; }
public string AudioLanguages { get; set; }
public string Subtitles { get; set; }
public string ScanType { get; set; }
}
}

View File

@ -145,6 +145,9 @@
<Reference Include="Ionic.Zip">
<HintPath>..\packages\DotNetZip.1.9.1.8\lib\net20\Ionic.Zip.dll</HintPath>
</Reference>
<Reference Include="MediaInfoDotNet">
<HintPath>..\packages\MediaInfoNet.0.3\lib\MediaInfoDotNet.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
@ -277,6 +280,7 @@
<Compile Include="Model\AtomicParsleyTitleType.cs" />
<Compile Include="Model\ConnectionInfoModel.cs" />
<Compile Include="Model\BacklogSettingType.cs" />
<Compile Include="Model\MediaInfoModel.cs" />
<Compile Include="Model\Nzbx\NzbxSearchItem.cs" />
<Compile Include="Model\Nzbx\NzbxRecentItem.cs" />
<Compile Include="Model\Nzbx\NzbxVotesModel.cs" />
@ -314,6 +318,7 @@
<Compile Include="AutofacSignalrDependencyResolver.cs" />
<Compile Include="Providers\BannerProvider.cs" />
<Compile Include="Providers\DecisionEngine\LanguageSpecification.cs" />
<Compile Include="Providers\MediaInfoProvider.cs" />
<Compile Include="Providers\SearchProvider2.cs" />
<Compile Include="Providers\DecisionEngine\AllowedReleaseGroupSpecification.cs" />
<Compile Include="Providers\DecisionEngine\CustomStartDateSpecification.cs" />
@ -618,6 +623,9 @@
<ItemGroup>
<Content Include="GettingStarted.txt" />
<Content Include="Json.NET.license.txt" />
<Content Include="MediaInfo.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="NzbDrone.ico" />
<Content Include="license.txt" />
</ItemGroup>

View File

@ -23,12 +23,13 @@ namespace NzbDrone.Core.Providers
private readonly SignalRProvider _signalRProvider;
private readonly ConfigProvider _configProvider;
private readonly RecycleBinProvider _recycleBinProvider;
private readonly MediaInfoProvider _mediaInfoProvider;
public DiskScanProvider(DiskProvider diskProvider, EpisodeProvider episodeProvider,
SeriesProvider seriesProvider, MediaFileProvider mediaFileProvider,
ExternalNotificationProvider externalNotificationProvider, DownloadProvider downloadProvider,
SignalRProvider signalRProvider, ConfigProvider configProvider,
RecycleBinProvider recycleBinProvider)
RecycleBinProvider recycleBinProvider, MediaInfoProvider mediaInfoProvider)
{
_diskProvider = diskProvider;
_episodeProvider = episodeProvider;
@ -39,6 +40,7 @@ namespace NzbDrone.Core.Providers
_signalRProvider = signalRProvider;
_configProvider = configProvider;
_recycleBinProvider = recycleBinProvider;
_mediaInfoProvider = mediaInfoProvider;
}
public DiskScanProvider()
@ -110,10 +112,9 @@ namespace NzbDrone.Core.Providers
}
long size = _diskProvider.GetSize(filePath);
var runTime = _mediaInfoProvider.GetRunTime(filePath);
//Todo: We shouldn't skip on file size alone, 64MB Family Guy episodes are skipped...
//Skip any file under 70MB - New samples don't even have sample in the name...
if (size < Constants.IgnoreFileSize)
if(size < Constants.IgnoreFileSize && runTime < 480)
{
Logger.Trace("[{0}] appears to be a sample. skipping.", filePath);
return null;

View File

@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using MediaInfoLib;
using NLog;
using NzbDrone.Common;
using NzbDrone.Core.Model;
namespace NzbDrone.Core.Providers
{
public class MediaInfoProvider
{
private readonly DiskProvider _diskProvider;
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
public MediaInfoProvider(DiskProvider diskProvider)
{
_diskProvider = diskProvider;
}
public MediaInfoProvider()
{
}
public virtual MediaInfoModel GetMediaInfo(string filename)
{
if (!_diskProvider.FileExists(filename))
throw new FileNotFoundException("Media file does not exist: " + filename);
var mediaInfo = new MediaInfo();
try
{
logger.Trace("Getting media info from {0}", filename);
mediaInfo.Option("ParseSpeed", "0.2");
int open = mediaInfo.Open(filename);
if (open != 0)
{
int width;
int height;
int videoBitRate;
int audioBitRate;
int runTime;
int streamCount;
int audioChannels;
string subtitles = mediaInfo.Get(StreamKind.General, 0, "Text_Language_List");
string scanType = mediaInfo.Get(StreamKind.Video, 0, "ScanType");
Int32.TryParse(mediaInfo.Get(StreamKind.Video, 0, "Width"), out width);
Int32.TryParse(mediaInfo.Get(StreamKind.Video, 0, "Height"), out height);
Int32.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitRate"), out videoBitRate);
string aBitRate = mediaInfo.Get(StreamKind.Audio, 0, "BitRate");
int ABindex = aBitRate.IndexOf(" /");
if (ABindex > 0)
aBitRate = aBitRate.Remove(ABindex);
Int32.TryParse(aBitRate, out audioBitRate);
Int32.TryParse(mediaInfo.Get(StreamKind.General, 0, "PlayTime"), out runTime);
Int32.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "StreamCount"), out streamCount);
string audioChannelsStr = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)");
int ACindex = audioChannelsStr.IndexOf(" /");
if (ACindex > 0)
audioChannelsStr = audioChannelsStr.Remove(ACindex);
string audioLanguages = mediaInfo.Get(StreamKind.General, 0, "Audio_Language_List");
decimal videoFrameRate = Decimal.Parse(mediaInfo.Get(StreamKind.Video, 0, "FrameRate"));
string audioProfile = mediaInfo.Get(StreamKind.Audio, 0, "Format_Profile");
int APindex = audioProfile.IndexOf(" /");
if (APindex > 0)
audioProfile = audioProfile.Remove(APindex);
Int32.TryParse(audioChannelsStr, out audioChannels);
var mediaInfoModel = new MediaInfoModel
{
VideoCodec = mediaInfo.Get(StreamKind.Video, 0, "Codec/String"),
VideoBitrate = videoBitRate,
Height = height,
Width = width,
AudioFormat = mediaInfo.Get(StreamKind.Audio, 0, "Format"),
AudioBitrate = audioBitRate,
RunTime = (runTime / 1000), //InSeconds
AudioStreamCount = streamCount,
AudioChannels = audioChannels,
AudioProfile = audioProfile.Trim(),
VideoFps = videoFrameRate,
AudioLanguages = audioLanguages,
Subtitles = subtitles,
ScanType = scanType
};
mediaInfo.Close();
return mediaInfoModel;
}
}
catch (Exception ex)
{
logger.ErrorException("Unable to parse media info from file: " + filename, ex);
mediaInfo.Close();
}
return null;
}
public virtual Int32 GetRunTime(string filename)
{
var mediaInfo = new MediaInfo();
try
{
logger.Trace("Getting media info from {0}", filename);
mediaInfo.Option("ParseSpeed", "0.2");
int open = mediaInfo.Open(filename);
if (open != 0)
{
int runTime;
Int32.TryParse(mediaInfo.Get(StreamKind.General, 0, "PlayTime"), out runTime);
mediaInfo.Close();
return runTime / 1000; //Convert to seconds
}
}
catch (Exception ex)
{
logger.ErrorException("Unable to parse media info from file: " + filename, ex);
mediaInfo.Close();
}
return 0;
}
}
}

View File

@ -4,6 +4,7 @@
<package id="DataTables.Mvc.Core" version="0.1.0.85" />
<package id="DotNetZip" version="1.9.1.8" />
<package id="Growl" version="0.6" />
<package id="MediaInfoNet" version="0.3" targetFramework="net40" />
<package id="Microsoft.SqlServer.Compact" version="4.0.8876.1" targetFramework="net40" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" />
<package id="MiniProfiler" version="2.0.2" />

View File

@ -16,7 +16,7 @@
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.5.0.0" newVersion="4.5.0.0" />
<bindingRedirect oldVersion="0.0.0.0-4.0.8.0" newVersion="4.0.8.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral" />

View File

@ -72,7 +72,7 @@
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.5.0.0" newVersion="4.5.0.0" />
<bindingRedirect oldVersion="0.0.0.0-4.0.8.0" newVersion="4.0.8.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>