MediaInfo added

New: Samples will only be skipped when under 70MB and under 8 minutes in
length
#ND-121 fixed
pull/6/head
Mark McDowall 12 years ago
parent eeb16d6d5a
commit e136b458e0

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

Binary file not shown.

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

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

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

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

@ -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" />

@ -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" />

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

Loading…
Cancel
Save