1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2024-12-16 11:37:58 +02:00

Added MediaInfo to EpisodeFile.

This commit is contained in:
Taloth Saldono 2014-04-10 19:58:50 +02:00
parent b427954f5f
commit 7b420fc033
14 changed files with 314 additions and 2 deletions

View File

@ -0,0 +1,118 @@
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
{
[TestFixture]
public class UpdateMediaInfoServiceFixture : CoreTest<UpdateMediaInfoService>
{
private void GivenFileExists()
{
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.FileExists(It.IsAny<String>()))
.Returns(true);
}
private void GivenSuccessfulScan()
{
Mocker.GetMock<IVideoFileInfoReader>()
.Setup(v => v.GetMediaInfo(It.IsAny<String>()))
.Returns(new MediaInfoModel());
}
private void GivenFailedScan(String path)
{
Mocker.GetMock<IVideoFileInfoReader>()
.Setup(v => v.GetMediaInfo(path))
.Returns((MediaInfoModel)null);
}
[Test]
public void should_get_for_existing_episodefile_on_after_series_scan()
{
var episodeFiles = Builder<EpisodeFile>.CreateListOfSize(3)
.All()
.With(v => v.Path = @"C:\series\media.mkv".AsOsAgnostic())
.TheFirst(1)
.With(v => v.MediaInfo = new MediaInfoModel())
.BuildList();
Mocker.GetMock<IMediaFileService>()
.Setup(v => v.GetFilesBySeries(1))
.Returns(episodeFiles);
GivenFileExists();
GivenSuccessfulScan();
Subject.Handle(new SeriesScannedEvent(new Tv.Series { Id = 1 }));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(@"C:\series\media.mkv".AsOsAgnostic()), Times.Exactly(2));
Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<EpisodeFile>()), Times.Exactly(2));
}
[Test]
public void should_ignore_missing_files()
{
var episodeFiles = Builder<EpisodeFile>.CreateListOfSize(2)
.All()
.With(v => v.Path = @"C:\series\media.mkv".AsOsAgnostic())
.BuildList();
Mocker.GetMock<IMediaFileService>()
.Setup(v => v.GetFilesBySeries(1))
.Returns(episodeFiles);
GivenSuccessfulScan();
Subject.Handle(new SeriesScannedEvent(new Tv.Series { Id = 1 }));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(@"C:\series\media.mkv".AsOsAgnostic()), Times.Never());
Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<EpisodeFile>()), Times.Never());
}
[Test]
public void should_continue_after_failure()
{
var episodeFiles = Builder<EpisodeFile>.CreateListOfSize(2)
.All()
.With(v => v.Path = @"C:\series\media.mkv".AsOsAgnostic())
.TheFirst(1)
.With(v => v.Path = @"C:\series\media2.mkv".AsOsAgnostic())
.BuildList();
Mocker.GetMock<IMediaFileService>()
.Setup(v => v.GetFilesBySeries(1))
.Returns(episodeFiles);
GivenFileExists();
GivenSuccessfulScan();
GivenFailedScan(@"C:\series\media2.mkv".AsOsAgnostic());
Subject.Handle(new SeriesScannedEvent(new Tv.Series { Id = 1 }));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(@"C:\series\media.mkv".AsOsAgnostic()), Times.Exactly(1));
Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<EpisodeFile>()), Times.Exactly(1));
}
}
}

View File

@ -190,6 +190,7 @@
<Compile Include="MediaFiles\MediaFileRepositoryFixture.cs" /> <Compile Include="MediaFiles\MediaFileRepositoryFixture.cs" />
<Compile Include="MediaFiles\MediaFileServiceTest.cs" /> <Compile Include="MediaFiles\MediaFileServiceTest.cs" />
<Compile Include="MediaFiles\MediaFileTableCleanupServiceFixture.cs" /> <Compile Include="MediaFiles\MediaFileTableCleanupServiceFixture.cs" />
<Compile Include="MediaFiles\MediaInfo\UpdateMediaInfoServiceFixture.cs" />
<Compile Include="MediaFiles\MediaInfo\VideoFileInfoReaderFixture.cs" /> <Compile Include="MediaFiles\MediaInfo\VideoFileInfoReaderFixture.cs" />
<Compile Include="MediaFiles\RenameEpisodeFileServiceFixture.cs" /> <Compile Include="MediaFiles\RenameEpisodeFileServiceFixture.cs" />
<Compile Include="MediaFiles\UpgradeMediaFileServiceFixture.cs" /> <Compile Include="MediaFiles\UpgradeMediaFileServiceFixture.cs" />

View File

@ -0,0 +1,15 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
using System.Data;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(56)]
public class add_mediainfo_to_episodefile : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("EpisodeFiles").AddColumn("MediaInfo").AsString().Nullable();
}
}
}

View File

@ -2,6 +2,7 @@
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.MediaFiles.MediaInfo;
namespace NzbDrone.Core.MediaFiles namespace NzbDrone.Core.MediaFiles
{ {
@ -15,6 +16,7 @@ public class EpisodeFile : ModelBase
public string SceneName { get; set; } public string SceneName { get; set; }
public string ReleaseGroup { get; set; } public string ReleaseGroup { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public MediaInfoModel MediaInfo { get; set; }
public LazyList<Episode> Episodes { get; set; } public LazyList<Episode> Episodes { get; set; }
public override string ToString() public override string ToString()

View File

@ -73,6 +73,7 @@ public List<ImportDecision> Import(List<ImportDecision> decisions, bool newDownl
episodeFile.Path = localEpisode.Path.CleanFilePath(); episodeFile.Path = localEpisode.Path.CleanFilePath();
episodeFile.Size = _diskProvider.GetFileSize(localEpisode.Path); episodeFile.Size = _diskProvider.GetFileSize(localEpisode.Path);
episodeFile.Quality = localEpisode.Quality; episodeFile.Quality = localEpisode.Quality;
episodeFile.MediaInfo = localEpisode.MediaInfo;
episodeFile.SeasonNumber = localEpisode.SeasonNumber; episodeFile.SeasonNumber = localEpisode.SeasonNumber;
episodeFile.Episodes = localEpisode.Episodes; episodeFile.Episodes = localEpisode.Episodes;
episodeFile.ReleaseGroup = localEpisode.ParsedEpisodeInfo.ReleaseGroup; episodeFile.ReleaseGroup = localEpisode.ParsedEpisodeInfo.ReleaseGroup;

View File

@ -8,6 +8,7 @@
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.MediaFiles.MediaInfo;
namespace NzbDrone.Core.MediaFiles.EpisodeImport namespace NzbDrone.Core.MediaFiles.EpisodeImport
@ -23,19 +24,21 @@ public class ImportDecisionMaker : IMakeImportDecision
private readonly IParsingService _parsingService; private readonly IParsingService _parsingService;
private readonly IMediaFileService _mediaFileService; private readonly IMediaFileService _mediaFileService;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IVideoFileInfoReader _videoFileInfoReader;
private readonly Logger _logger; private readonly Logger _logger;
public ImportDecisionMaker(IEnumerable<IRejectWithReason> specifications, public ImportDecisionMaker(IEnumerable<IRejectWithReason> specifications,
IParsingService parsingService, IParsingService parsingService,
IMediaFileService mediaFileService, IMediaFileService mediaFileService,
IDiskProvider diskProvider, IDiskProvider diskProvider,
IVideoFileInfoReader videoFileInfoReader,
Logger logger) Logger logger)
{ {
_specifications = specifications; _specifications = specifications;
_parsingService = parsingService; _parsingService = parsingService;
_mediaFileService = mediaFileService; _mediaFileService = mediaFileService;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_videoFileInfoReader = videoFileInfoReader;
_logger = logger; _logger = logger;
} }
@ -69,6 +72,8 @@ private IEnumerable<ImportDecision> GetDecisions(IEnumerable<String> videoFiles,
parsedEpisode.Size = _diskProvider.GetFileSize(file); parsedEpisode.Size = _diskProvider.GetFileSize(file);
_logger.Debug("Size: {0}", parsedEpisode.Size); _logger.Debug("Size: {0}", parsedEpisode.Size);
parsedEpisode.MediaInfo = _videoFileInfoReader.GetMediaInfo(file);
decision = GetDecision(parsedEpisode); decision = GetDecision(parsedEpisode);
} }

View File

@ -9,6 +9,7 @@ public interface IMediaFileRepository : IBasicRepository<EpisodeFile>
{ {
List<EpisodeFile> GetFilesBySeries(int seriesId); List<EpisodeFile> GetFilesBySeries(int seriesId);
List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber); List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber);
List<EpisodeFile> GetFilesWithoutMediaInfo();
} }
@ -30,5 +31,10 @@ public List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber)
.AndWhere(c => c.SeasonNumber == seasonNumber) .AndWhere(c => c.SeasonNumber == seasonNumber)
.ToList(); .ToList();
} }
public List<EpisodeFile> GetFilesWithoutMediaInfo()
{
return Query.Where(c => c.MediaInfo == null).ToList();
}
} }
} }

View File

@ -15,6 +15,7 @@ public interface IMediaFileService
void Delete(EpisodeFile episodeFile, bool forUpgrade = false); void Delete(EpisodeFile episodeFile, bool forUpgrade = false);
List<EpisodeFile> GetFilesBySeries(int seriesId); List<EpisodeFile> GetFilesBySeries(int seriesId);
List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber); List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber);
List<EpisodeFile> GetFilesWithoutMediaInfo();
List<string> FilterExistingFiles(List<string> files, int seriesId); List<string> FilterExistingFiles(List<string> files, int seriesId);
EpisodeFile Get(int id); EpisodeFile Get(int id);
List<EpisodeFile> Get(IEnumerable<int> ids); List<EpisodeFile> Get(IEnumerable<int> ids);
@ -62,6 +63,11 @@ public List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber)
return _mediaFileRepository.GetFilesBySeason(seriesId, seasonNumber); return _mediaFileRepository.GetFilesBySeason(seriesId, seasonNumber);
} }
public List<EpisodeFile> GetFilesWithoutMediaInfo()
{
return _mediaFileRepository.GetFilesWithoutMediaInfo();
}
public List<string> FilterExistingFiles(List<string> files, int seriesId) public List<string> FilterExistingFiles(List<string> files, int seriesId)
{ {
var seriesFiles = GetFilesBySeries(seriesId).Select(f => f.Path).ToList(); var seriesFiles = GetFilesBySeries(seriesId).Select(f => f.Path).ToList();

View File

@ -1,8 +1,9 @@
using System; using System;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.MediaFiles.MediaInfo namespace NzbDrone.Core.MediaFiles.MediaInfo
{ {
public class MediaInfoModel public class MediaInfoModel : IEmbeddedDocument
{ {
public string VideoCodec { get; set; } public string VideoCodec { get; set; }
public int VideoBitrate { get; set; } public int VideoBitrate { get; set; }

View File

@ -0,0 +1,63 @@
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MediaFiles.MediaInfo
{
public class UpdateMediaInfoService : IHandle<SeriesScannedEvent>
{
private readonly IDiskProvider _diskProvider;
private readonly IMediaFileService _mediaFileService;
private readonly IVideoFileInfoReader _videoFileInfoReader;
private readonly Logger _logger;
public UpdateMediaInfoService(IDiskProvider diskProvider,
IMediaFileService mediaFileService,
IVideoFileInfoReader videoFileInfoReader,
Logger logger)
{
_diskProvider = diskProvider;
_mediaFileService = mediaFileService;
_videoFileInfoReader = videoFileInfoReader;
_logger = logger;
}
private void UpdateMediaInfo(List<EpisodeFile> mediaFiles)
{
foreach (var mediaFile in mediaFiles)
{
var path = mediaFile.Path;
if (!_diskProvider.FileExists(path))
{
_logger.Debug("Can't update MediaInfo because '{0}' does not exist", path);
continue;
}
mediaFile.MediaInfo = _videoFileInfoReader.GetMediaInfo(path);
if (mediaFile.MediaInfo != null)
{
_mediaFileService.Update(mediaFile);
_logger.Debug("Updated MediaInfo for '{0}'", path);
}
}
}
public void Handle(SeriesScannedEvent message)
{
var mediaFiles = _mediaFileService.GetFilesBySeries(message.Series.Id)
.Where(c => c.MediaInfo == null)
.ToList();
UpdateMediaInfo(mediaFiles);
}
}
}

View File

@ -212,6 +212,7 @@
<Compile Include="Datastore\Migration\053_add_series_sorttitle.cs" /> <Compile Include="Datastore\Migration\053_add_series_sorttitle.cs" />
<Compile Include="Datastore\Migration\054_rename_profiles.cs" /> <Compile Include="Datastore\Migration\054_rename_profiles.cs" />
<Compile Include="Datastore\Migration\055_drop_old_profile_columns.cs" /> <Compile Include="Datastore\Migration\055_drop_old_profile_columns.cs" />
<Compile Include="Datastore\Migration\056_add_mediainfo_to_episodefile.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" /> <Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" /> <Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" /> <Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" />
@ -482,6 +483,7 @@
</Compile> </Compile>
<Compile Include="MediaFiles\MediaFileTableCleanupService.cs" /> <Compile Include="MediaFiles\MediaFileTableCleanupService.cs" />
<Compile Include="MediaFiles\MediaInfo\MediaInfoModel.cs" /> <Compile Include="MediaFiles\MediaInfo\MediaInfoModel.cs" />
<Compile Include="MediaFiles\MediaInfo\UpdateMediaInfoService.cs" />
<Compile Include="MediaFiles\MediaInfo\VideoFileInfoReader.cs" /> <Compile Include="MediaFiles\MediaInfo\VideoFileInfoReader.cs" />
<Compile Include="MediaFiles\RecycleBinProvider.cs" /> <Compile Include="MediaFiles\RecycleBinProvider.cs" />
<Compile Include="MediaFiles\RenameEpisodeFilePreview.cs" /> <Compile Include="MediaFiles\RenameEpisodeFilePreview.cs" />

View File

@ -216,6 +216,8 @@ public string BuildFilename(IList<Episode> episodes, Series series, EpisodeFile
tokenValues.Add("{Episode Title}", GetEpisodeTitle(episodeTitles)); tokenValues.Add("{Episode Title}", GetEpisodeTitle(episodeTitles));
tokenValues.Add("{Quality Title}", GetQualityTitle(episodeFile.Quality)); tokenValues.Add("{Quality Title}", GetQualityTitle(episodeFile.Quality));
AddMediaInfoTokens(episodeFile, tokenValues);
var filename = ReplaceTokens(pattern, tokenValues).Trim(); var filename = ReplaceTokens(pattern, tokenValues).Trim();
filename = FilenameCleanupRegex.Replace(filename, match => match.Captures[0].Value[0].ToString() ); filename = FilenameCleanupRegex.Replace(filename, match => match.Captures[0].Value[0].ToString() );
@ -333,6 +335,91 @@ public static string CleanFilename(string name)
return result.Trim(); return result.Trim();
} }
private void AddMediaInfoTokens(EpisodeFile episodeFile, Dictionary<string, string> tokenValues)
{
if (episodeFile.MediaInfo == null)
return;
var mediaInfoFull = string.Empty;
switch (episodeFile.MediaInfo.VideoCodec)
{
case "AVC":
if (Path.GetFileNameWithoutExtension(episodeFile.Path).Contains("x264"))
mediaInfoFull += "x264";
else if (Path.GetFileNameWithoutExtension(episodeFile.Path).Contains("h264"))
mediaInfoFull += "h264";
else
mediaInfoFull += "h264";
break;
default:
mediaInfoFull += episodeFile.MediaInfo.VideoCodec;
break;
}
switch (episodeFile.MediaInfo.AudioFormat)
{
case "AC-3":
mediaInfoFull += ".AC3";
break;
case "MPEG Audio":
if (episodeFile.MediaInfo.AudioProfile == "Layer 3")
mediaInfoFull += ".MP3";
else
mediaInfoFull += "." + episodeFile.MediaInfo.AudioFormat;
break;
case "DTS":
mediaInfoFull += "." + episodeFile.MediaInfo.AudioFormat;
break;
default:
mediaInfoFull += "." + episodeFile.MediaInfo.AudioFormat;
break;
}
tokenValues.Add("{MediaInfo Short}", mediaInfoFull);
var audioLanguagesToken = GetLanguagesToken(episodeFile.MediaInfo.AudioLanguages);
if (!string.IsNullOrEmpty(audioLanguagesToken) && audioLanguagesToken != "EN")
mediaInfoFull += string.Format("[{0}]", audioLanguagesToken);
var subtitleLanguagesToken = GetLanguagesToken(episodeFile.MediaInfo.Subtitles);
if (!string.IsNullOrEmpty(subtitleLanguagesToken))
mediaInfoFull += string.Format(".[{0}]", subtitleLanguagesToken);
tokenValues.Add("{MediaInfo Full}", mediaInfoFull);
}
private string GetLanguagesToken(string mediaInfoLanguages)
{
List<string> tokens = new List<string>();
foreach (var item in mediaInfoLanguages.Split('/'))
{
if (!string.IsNullOrWhiteSpace(item))
tokens.Add(item.Trim());
}
var cultures = System.Globalization.CultureInfo.GetCultures(System.Globalization.CultureTypes.NeutralCultures);
for (int i = 0; i < tokens.Count; i++)
{
try
{
var cultureInfo = cultures.FirstOrDefault(p => p.EnglishName == tokens[i]);
if (cultureInfo != null)
tokens[i] = cultureInfo.TwoLetterISOLanguageName.ToUpper();
}
catch
{
}
}
return string.Join("+", tokens.Distinct());
}
private string ReplaceTokens(string pattern, Dictionary<string, string> tokenValues) private string ReplaceTokens(string pattern, Dictionary<string, string> tokenValues)
{ {
return TitleRegex.Replace(pattern, match => ReplaceToken(match, tokenValues)); return TitleRegex.Replace(pattern, match => ReplaceToken(match, tokenValues));

View File

@ -3,6 +3,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.MediaFiles.MediaInfo;
namespace NzbDrone.Core.Parser.Model namespace NzbDrone.Core.Parser.Model
{ {
@ -14,6 +15,7 @@ public class LocalEpisode
public Series Series { get; set; } public Series Series { get; set; }
public List<Episode> Episodes { get; set; } public List<Episode> Episodes { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public MediaInfoModel MediaInfo { get; set; }
public Boolean ExistingFile { get; set; } public Boolean ExistingFile { get; set; }
public int SeasonNumber public int SeasonNumber

View File

@ -27,16 +27,19 @@ public class ParsingService : IParsingService
private readonly IEpisodeService _episodeService; private readonly IEpisodeService _episodeService;
private readonly ISeriesService _seriesService; private readonly ISeriesService _seriesService;
private readonly ISceneMappingService _sceneMappingService; private readonly ISceneMappingService _sceneMappingService;
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger; private readonly Logger _logger;
public ParsingService(IEpisodeService episodeService, public ParsingService(IEpisodeService episodeService,
ISeriesService seriesService, ISeriesService seriesService,
ISceneMappingService sceneMappingService, ISceneMappingService sceneMappingService,
IDiskProvider diskProvider,
Logger logger) Logger logger)
{ {
_episodeService = episodeService; _episodeService = episodeService;
_seriesService = seriesService; _seriesService = seriesService;
_sceneMappingService = sceneMappingService; _sceneMappingService = sceneMappingService;
_diskProvider = diskProvider;
_logger = logger; _logger = logger;
} }