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:
parent
b427954f5f
commit
7b420fc033
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -190,6 +190,7 @@
|
||||
<Compile Include="MediaFiles\MediaFileRepositoryFixture.cs" />
|
||||
<Compile Include="MediaFiles\MediaFileServiceTest.cs" />
|
||||
<Compile Include="MediaFiles\MediaFileTableCleanupServiceFixture.cs" />
|
||||
<Compile Include="MediaFiles\MediaInfo\UpdateMediaInfoServiceFixture.cs" />
|
||||
<Compile Include="MediaFiles\MediaInfo\VideoFileInfoReaderFixture.cs" />
|
||||
<Compile Include="MediaFiles\RenameEpisodeFileServiceFixture.cs" />
|
||||
<Compile Include="MediaFiles\UpgradeMediaFileServiceFixture.cs" />
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles
|
||||
{
|
||||
@ -15,6 +16,7 @@ public class EpisodeFile : ModelBase
|
||||
public string SceneName { get; set; }
|
||||
public string ReleaseGroup { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public MediaInfoModel MediaInfo { get; set; }
|
||||
public LazyList<Episode> Episodes { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
|
@ -73,6 +73,7 @@ public List<ImportDecision> Import(List<ImportDecision> decisions, bool newDownl
|
||||
episodeFile.Path = localEpisode.Path.CleanFilePath();
|
||||
episodeFile.Size = _diskProvider.GetFileSize(localEpisode.Path);
|
||||
episodeFile.Quality = localEpisode.Quality;
|
||||
episodeFile.MediaInfo = localEpisode.MediaInfo;
|
||||
episodeFile.SeasonNumber = localEpisode.SeasonNumber;
|
||||
episodeFile.Episodes = localEpisode.Episodes;
|
||||
episodeFile.ReleaseGroup = localEpisode.ParsedEpisodeInfo.ReleaseGroup;
|
||||
|
@ -8,6 +8,7 @@
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||
@ -23,19 +24,21 @@ public class ImportDecisionMaker : IMakeImportDecision
|
||||
private readonly IParsingService _parsingService;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IVideoFileInfoReader _videoFileInfoReader;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ImportDecisionMaker(IEnumerable<IRejectWithReason> specifications,
|
||||
IParsingService parsingService,
|
||||
IMediaFileService mediaFileService,
|
||||
IDiskProvider diskProvider,
|
||||
|
||||
IVideoFileInfoReader videoFileInfoReader,
|
||||
Logger logger)
|
||||
{
|
||||
_specifications = specifications;
|
||||
_parsingService = parsingService;
|
||||
_mediaFileService = mediaFileService;
|
||||
_diskProvider = diskProvider;
|
||||
_videoFileInfoReader = videoFileInfoReader;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -69,6 +72,8 @@ private IEnumerable<ImportDecision> GetDecisions(IEnumerable<String> videoFiles,
|
||||
parsedEpisode.Size = _diskProvider.GetFileSize(file);
|
||||
_logger.Debug("Size: {0}", parsedEpisode.Size);
|
||||
|
||||
parsedEpisode.MediaInfo = _videoFileInfoReader.GetMediaInfo(file);
|
||||
|
||||
decision = GetDecision(parsedEpisode);
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ public interface IMediaFileRepository : IBasicRepository<EpisodeFile>
|
||||
{
|
||||
List<EpisodeFile> GetFilesBySeries(int seriesId);
|
||||
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)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public List<EpisodeFile> GetFilesWithoutMediaInfo()
|
||||
{
|
||||
return Query.Where(c => c.MediaInfo == null).ToList();
|
||||
}
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ public interface IMediaFileService
|
||||
void Delete(EpisodeFile episodeFile, bool forUpgrade = false);
|
||||
List<EpisodeFile> GetFilesBySeries(int seriesId);
|
||||
List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber);
|
||||
List<EpisodeFile> GetFilesWithoutMediaInfo();
|
||||
List<string> FilterExistingFiles(List<string> files, int seriesId);
|
||||
EpisodeFile Get(int id);
|
||||
List<EpisodeFile> Get(IEnumerable<int> ids);
|
||||
@ -62,6 +63,11 @@ public List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber)
|
||||
return _mediaFileRepository.GetFilesBySeason(seriesId, seasonNumber);
|
||||
}
|
||||
|
||||
public List<EpisodeFile> GetFilesWithoutMediaInfo()
|
||||
{
|
||||
return _mediaFileRepository.GetFilesWithoutMediaInfo();
|
||||
}
|
||||
|
||||
public List<string> FilterExistingFiles(List<string> files, int seriesId)
|
||||
{
|
||||
var seriesFiles = GetFilesBySeries(seriesId).Select(f => f.Path).ToList();
|
||||
|
@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||
{
|
||||
public class MediaInfoModel
|
||||
public class MediaInfoModel : IEmbeddedDocument
|
||||
{
|
||||
public string VideoCodec { get; set; }
|
||||
public int VideoBitrate { get; set; }
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -212,6 +212,7 @@
|
||||
<Compile Include="Datastore\Migration\053_add_series_sorttitle.cs" />
|
||||
<Compile Include="Datastore\Migration\054_rename_profiles.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\MigrationController.cs" />
|
||||
<Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" />
|
||||
@ -482,6 +483,7 @@
|
||||
</Compile>
|
||||
<Compile Include="MediaFiles\MediaFileTableCleanupService.cs" />
|
||||
<Compile Include="MediaFiles\MediaInfo\MediaInfoModel.cs" />
|
||||
<Compile Include="MediaFiles\MediaInfo\UpdateMediaInfoService.cs" />
|
||||
<Compile Include="MediaFiles\MediaInfo\VideoFileInfoReader.cs" />
|
||||
<Compile Include="MediaFiles\RecycleBinProvider.cs" />
|
||||
<Compile Include="MediaFiles\RenameEpisodeFilePreview.cs" />
|
||||
|
@ -216,6 +216,8 @@ public string BuildFilename(IList<Episode> episodes, Series series, EpisodeFile
|
||||
tokenValues.Add("{Episode Title}", GetEpisodeTitle(episodeTitles));
|
||||
tokenValues.Add("{Quality Title}", GetQualityTitle(episodeFile.Quality));
|
||||
|
||||
AddMediaInfoTokens(episodeFile, tokenValues);
|
||||
|
||||
var filename = ReplaceTokens(pattern, tokenValues).Trim();
|
||||
filename = FilenameCleanupRegex.Replace(filename, match => match.Captures[0].Value[0].ToString() );
|
||||
|
||||
@ -333,6 +335,91 @@ public static string CleanFilename(string name)
|
||||
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)
|
||||
{
|
||||
return TitleRegex.Replace(pattern, match => ReplaceToken(match, tokenValues));
|
||||
|
@ -3,6 +3,7 @@
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
|
||||
namespace NzbDrone.Core.Parser.Model
|
||||
{
|
||||
@ -14,6 +15,7 @@ public class LocalEpisode
|
||||
public Series Series { get; set; }
|
||||
public List<Episode> Episodes { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public MediaInfoModel MediaInfo { get; set; }
|
||||
public Boolean ExistingFile { get; set; }
|
||||
|
||||
public int SeasonNumber
|
||||
|
@ -27,16 +27,19 @@ public class ParsingService : IParsingService
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly ISceneMappingService _sceneMappingService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ParsingService(IEpisodeService episodeService,
|
||||
ISeriesService seriesService,
|
||||
ISceneMappingService sceneMappingService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
{
|
||||
_episodeService = episodeService;
|
||||
_seriesService = seriesService;
|
||||
_sceneMappingService = sceneMappingService;
|
||||
_diskProvider = diskProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user