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

View File

@ -23,12 +23,13 @@ namespace NzbDrone.Core.Providers
private readonly SignalRProvider _signalRProvider; private readonly SignalRProvider _signalRProvider;
private readonly ConfigProvider _configProvider; private readonly ConfigProvider _configProvider;
private readonly RecycleBinProvider _recycleBinProvider; private readonly RecycleBinProvider _recycleBinProvider;
private readonly MediaInfoProvider _mediaInfoProvider;
public DiskScanProvider(DiskProvider diskProvider, EpisodeProvider episodeProvider, public DiskScanProvider(DiskProvider diskProvider, EpisodeProvider episodeProvider,
SeriesProvider seriesProvider, MediaFileProvider mediaFileProvider, SeriesProvider seriesProvider, MediaFileProvider mediaFileProvider,
ExternalNotificationProvider externalNotificationProvider, DownloadProvider downloadProvider, ExternalNotificationProvider externalNotificationProvider, DownloadProvider downloadProvider,
SignalRProvider signalRProvider, ConfigProvider configProvider, SignalRProvider signalRProvider, ConfigProvider configProvider,
RecycleBinProvider recycleBinProvider) RecycleBinProvider recycleBinProvider, MediaInfoProvider mediaInfoProvider)
{ {
_diskProvider = diskProvider; _diskProvider = diskProvider;
_episodeProvider = episodeProvider; _episodeProvider = episodeProvider;
@ -39,6 +40,7 @@ namespace NzbDrone.Core.Providers
_signalRProvider = signalRProvider; _signalRProvider = signalRProvider;
_configProvider = configProvider; _configProvider = configProvider;
_recycleBinProvider = recycleBinProvider; _recycleBinProvider = recycleBinProvider;
_mediaInfoProvider = mediaInfoProvider;
} }
public DiskScanProvider() public DiskScanProvider()
@ -110,10 +112,9 @@ namespace NzbDrone.Core.Providers
} }
long size = _diskProvider.GetSize(filePath); 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 && runTime < 480)
if (size < Constants.IgnoreFileSize)
{ {
Logger.Trace("[{0}] appears to be a sample. skipping.", filePath); Logger.Trace("[{0}] appears to be a sample. skipping.", filePath);
return null; 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="DataTables.Mvc.Core" version="0.1.0.85" />
<package id="DotNetZip" version="1.9.1.8" /> <package id="DotNetZip" version="1.9.1.8" />
<package id="Growl" version="0.6" /> <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.SqlServer.Compact" version="4.0.8876.1" targetFramework="net40" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" /> <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" />
<package id="MiniProfiler" version="2.0.2" /> <package id="MiniProfiler" version="2.0.2" />

View File

@ -16,7 +16,7 @@
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" /> <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>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral" />

View File

@ -72,7 +72,7 @@
</dependentAssembly> </dependentAssembly>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" /> <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>
</assemblyBinding> </assemblyBinding>
</runtime> </runtime>