mirror of
https://github.com/Radarr/Radarr.git
synced 2024-09-11 12:02:35 +02:00
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:
parent
eeb16d6d5a
commit
e136b458e0
@ -324,7 +324,7 @@ public void import_new_multi_part_file_episode_replace_two_files()
|
||||
}
|
||||
|
||||
[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 @@ public void import_new_episode_no_existing_episode_file()
|
||||
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 @@ public void should_not_set_parseResult_SceneSource_if_in_series_Path()
|
||||
|
||||
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
BIN
NzbDrone.Core/MediaInfo.dll
Normal file
Binary file not shown.
25
NzbDrone.Core/Model/MediaInfoModel.cs
Normal file
25
NzbDrone.Core/Model/MediaInfoModel.cs
Normal 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; }
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -23,12 +23,13 @@ public class DiskScanProvider
|
||||
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 @@ public DiskScanProvider(DiskProvider diskProvider, EpisodeProvider episodeProvid
|
||||
_signalRProvider = signalRProvider;
|
||||
_configProvider = configProvider;
|
||||
_recycleBinProvider = recycleBinProvider;
|
||||
_mediaInfoProvider = mediaInfoProvider;
|
||||
}
|
||||
|
||||
public DiskScanProvider()
|
||||
@ -110,10 +112,9 @@ public virtual EpisodeFile ImportFile(Series series, string filePath)
|
||||
}
|
||||
|
||||
long size = _diskProvider.GetSize(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)
|
||||
var runTime = _mediaInfoProvider.GetRunTime(filePath);
|
||||
|
||||
if(size < Constants.IgnoreFileSize && runTime < 480)
|
||||
{
|
||||
Logger.Trace("[{0}] appears to be a sample. skipping.", filePath);
|
||||
return null;
|
||||
|
141
NzbDrone.Core/Providers/MediaInfoProvider.cs
Normal file
141
NzbDrone.Core/Providers/MediaInfoProvider.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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" />
|
||||
|
@ -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" />
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user