mirror of
https://github.com/Sonarr/Sonarr.git
synced 2024-10-29 23:12:39 +01:00
New: Added {MediaInfo VideoDynamicRange} renaming token to include HDR in the filename
This commit is contained in:
parent
8e486da928
commit
70c320e98b
@ -93,7 +93,8 @@ const mediaInfoTokens = [
|
|||||||
{ token: '{MediaInfo Full}', example: 'x264 DTS [EN+DE]' },
|
{ token: '{MediaInfo Full}', example: 'x264 DTS [EN+DE]' },
|
||||||
{ token: '{MediaInfo VideoCodec}', example: 'x264' },
|
{ token: '{MediaInfo VideoCodec}', example: 'x264' },
|
||||||
{ token: '{MediaInfo AudioFormat}', example: 'DTS' },
|
{ token: '{MediaInfo AudioFormat}', example: 'DTS' },
|
||||||
{ token: '{MediaInfo AudioChannels}', example: '5.1' }
|
{ token: '{MediaInfo AudioChannels}', example: '5.1' },
|
||||||
|
{ token: '{MediaInfo VideoDynamicRange}', example: 'HDR' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const otherTokens = [
|
const otherTokens = [
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class FormatVideoDynamicRangeFixture : TestBase
|
||||||
|
{
|
||||||
|
[TestCase(8, "BT.601 NTSC", "BT.709", "")]
|
||||||
|
[TestCase(10, "BT.2020", "PQ", "HDR")]
|
||||||
|
[TestCase(8, "BT.2020", "PQ", "")]
|
||||||
|
[TestCase(10, "BT.601 NTSC", "PQ", "")]
|
||||||
|
[TestCase(10, "BT.2020", "BT.709", "")]
|
||||||
|
[TestCase(10, "BT.2020", "HLG", "HDR")]
|
||||||
|
public void should_format_video_dynamic_range(int bitDepth, string colourPrimaries, string transferCharacteristics, string expectedVideoDynamicRange)
|
||||||
|
{
|
||||||
|
var mediaInfo = new MediaInfoModel
|
||||||
|
{
|
||||||
|
VideoBitDepth = bitDepth,
|
||||||
|
VideoColourPrimaries = colourPrimaries,
|
||||||
|
VideoTransferCharacteristics = transferCharacteristics,
|
||||||
|
SchemaRevision = 5
|
||||||
|
};
|
||||||
|
|
||||||
|
MediaInfoFormatter.FormatVideoDynamicRange(mediaInfo).Should().Be(expectedVideoDynamicRange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
@ -180,5 +181,111 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
|||||||
Mocker.GetMock<IMediaFileService>()
|
Mocker.GetMock<IMediaFileService>()
|
||||||
.Verify(v => v.Update(It.IsAny<EpisodeFile>()), Times.Exactly(1));
|
.Verify(v => v.Update(It.IsAny<EpisodeFile>()), Times.Exactly(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_update_files_if_media_info_disabled()
|
||||||
|
{
|
||||||
|
var episodeFiles = Builder<EpisodeFile>.CreateListOfSize(2)
|
||||||
|
.All()
|
||||||
|
.With(v => v.RelativePath = "media.mkv")
|
||||||
|
.TheFirst(1)
|
||||||
|
.With(v => v.RelativePath = "media2.mkv")
|
||||||
|
.BuildList();
|
||||||
|
|
||||||
|
Mocker.GetMock<IMediaFileService>()
|
||||||
|
.Setup(v => v.GetFilesBySeries(1))
|
||||||
|
.Returns(episodeFiles);
|
||||||
|
|
||||||
|
Mocker.GetMock<IConfigService>()
|
||||||
|
.SetupGet(s => s.EnableMediaInfo)
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
GivenFileExists();
|
||||||
|
GivenSuccessfulScan();
|
||||||
|
|
||||||
|
Subject.Handle(new SeriesScannedEvent(_series));
|
||||||
|
|
||||||
|
Mocker.GetMock<IVideoFileInfoReader>()
|
||||||
|
.Verify(v => v.GetMediaInfo(It.IsAny<string>()), Times.Never());
|
||||||
|
|
||||||
|
Mocker.GetMock<IMediaFileService>()
|
||||||
|
.Verify(v => v.Update(It.IsAny<EpisodeFile>()), Times.Never());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_update_if_media_info_disabled()
|
||||||
|
{
|
||||||
|
var episodeFile = Builder<EpisodeFile>.CreateNew()
|
||||||
|
.With(v => v.RelativePath = "media.mkv")
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
Mocker.GetMock<IConfigService>()
|
||||||
|
.SetupGet(s => s.EnableMediaInfo)
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
GivenFileExists();
|
||||||
|
GivenSuccessfulScan();
|
||||||
|
|
||||||
|
Subject.Update(episodeFile, _series);
|
||||||
|
|
||||||
|
Mocker.GetMock<IVideoFileInfoReader>()
|
||||||
|
.Verify(v => v.GetMediaInfo(It.IsAny<string>()), Times.Never());
|
||||||
|
|
||||||
|
Mocker.GetMock<IMediaFileService>()
|
||||||
|
.Verify(v => v.Update(It.IsAny<EpisodeFile>()), Times.Never());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_update_media_info()
|
||||||
|
{
|
||||||
|
var episodeFile = Builder<EpisodeFile>.CreateNew()
|
||||||
|
.With(v => v.RelativePath = "media.mkv")
|
||||||
|
.With(e => e.MediaInfo = new MediaInfoModel{SchemaRevision = 3})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
GivenFileExists();
|
||||||
|
GivenSuccessfulScan();
|
||||||
|
|
||||||
|
Subject.Update(episodeFile, _series);
|
||||||
|
|
||||||
|
Mocker.GetMock<IVideoFileInfoReader>()
|
||||||
|
.Verify(v => v.GetMediaInfo(Path.Combine(_series.Path, "media.mkv")), Times.Once());
|
||||||
|
|
||||||
|
Mocker.GetMock<IMediaFileService>()
|
||||||
|
.Verify(v => v.Update(episodeFile), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_update_media_info_if_new_info_is_null()
|
||||||
|
{
|
||||||
|
var episodeFile = Builder<EpisodeFile>.CreateNew()
|
||||||
|
.With(v => v.RelativePath = "media.mkv")
|
||||||
|
.With(e => e.MediaInfo = new MediaInfoModel{SchemaRevision = 3})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
GivenFileExists();
|
||||||
|
GivenFailedScan(Path.Combine(_series.Path, "media.mkv"));
|
||||||
|
|
||||||
|
Subject.Update(episodeFile, _series);
|
||||||
|
|
||||||
|
episodeFile.MediaInfo.Should().NotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_save_episode_file_if_new_info_is_null()
|
||||||
|
{
|
||||||
|
var episodeFile = Builder<EpisodeFile>.CreateNew()
|
||||||
|
.With(v => v.RelativePath = "media.mkv")
|
||||||
|
.With(e => e.MediaInfo = new MediaInfoModel{SchemaRevision = 3})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
GivenFileExists();
|
||||||
|
GivenFailedScan(Path.Combine(_series.Path, "media.mkv"));
|
||||||
|
|
||||||
|
Subject.Update(episodeFile, _series);
|
||||||
|
|
||||||
|
Mocker.GetMock<IMediaFileService>()
|
||||||
|
.Verify(v => v.Update(episodeFile), Times.Never());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -328,6 +328,7 @@
|
|||||||
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatterTests\FormatAudioCodecFixture.cs" />
|
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatterTests\FormatAudioCodecFixture.cs" />
|
||||||
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatterTests\FormatVideoCodecFixture.cs" />
|
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatterTests\FormatVideoCodecFixture.cs" />
|
||||||
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatterTests\FormatAudioChannelsFixture.cs" />
|
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatterTests\FormatAudioChannelsFixture.cs" />
|
||||||
|
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatterTests\FormatVideoDynamicRangeFixture.cs" />
|
||||||
<Compile Include="Messaging\Commands\CommandQueueManagerFixture.cs" />
|
<Compile Include="Messaging\Commands\CommandQueueManagerFixture.cs" />
|
||||||
<Compile Include="MetadataSource\SkyHook\SkyHookProxySearchFixture.cs" />
|
<Compile Include="MetadataSource\SkyHook\SkyHookProxySearchFixture.cs" />
|
||||||
<Compile Include="MetadataSource\SearchSeriesComparerFixture.cs" />
|
<Compile Include="MetadataSource\SearchSeriesComparerFixture.cs" />
|
||||||
|
@ -3,8 +3,10 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||||
using NzbDrone.Core.Organizer;
|
using NzbDrone.Core.Organizer;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
@ -738,5 +740,121 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
|||||||
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
|
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
|
||||||
.Should().Be(releaseGroup);
|
.Should().Be(releaseGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase(8, "BT.601 NTSC", "BT.709", "South.Park.S15E06.City.Sushi")]
|
||||||
|
[TestCase(10, "BT.2020", "PQ", "South.Park.S15E06.City.Sushi.HDR")]
|
||||||
|
[TestCase(10, "BT.2020", "HLG", "South.Park.S15E06.City.Sushi.HDR")]
|
||||||
|
[TestCase(0, null, null, "South.Park.S15E06.City.Sushi")]
|
||||||
|
public void should_include_hdr_for_mediainfo_videodynamicrange_with_valid_properties(int bitDepth, string colourPrimaries,
|
||||||
|
string transferCharacteristics, string expectedName)
|
||||||
|
{
|
||||||
|
_namingConfig.StandardEpisodeFormat =
|
||||||
|
"{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}.{MediaInfo VideoDynamicRange}";
|
||||||
|
|
||||||
|
GivenMediaInfoModel(videoBitDepth: bitDepth, videoColourPrimaries: colourPrimaries, videoTransferCharacteristics: transferCharacteristics);
|
||||||
|
|
||||||
|
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile)
|
||||||
|
.Should().Be(expectedName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_update_media_info_if_token_configured_and_revision_is_old()
|
||||||
|
{
|
||||||
|
_namingConfig.StandardEpisodeFormat =
|
||||||
|
"{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}.{MediaInfo VideoDynamicRange}";
|
||||||
|
|
||||||
|
GivenMediaInfoModel(schemaRevision: 3);
|
||||||
|
|
||||||
|
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile);
|
||||||
|
|
||||||
|
Mocker.GetMock<IUpdateMediaInfo>().Verify(v => v.Update(_episodeFile, _series), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_update_media_info_if_token_not_configured_and_revision_is_old()
|
||||||
|
{
|
||||||
|
_namingConfig.StandardEpisodeFormat =
|
||||||
|
"{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}";
|
||||||
|
|
||||||
|
GivenMediaInfoModel(schemaRevision: 3);
|
||||||
|
|
||||||
|
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile);
|
||||||
|
|
||||||
|
Mocker.GetMock<IUpdateMediaInfo>().Verify(v => v.Update(_episodeFile, _series), Times.Never());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_update_media_info_if_token_configured_and_revision_is_current()
|
||||||
|
{
|
||||||
|
_namingConfig.StandardEpisodeFormat =
|
||||||
|
"{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}.{MediaInfo VideoDynamicRange}";
|
||||||
|
|
||||||
|
GivenMediaInfoModel(schemaRevision: 5);
|
||||||
|
|
||||||
|
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile);
|
||||||
|
|
||||||
|
Mocker.GetMock<IUpdateMediaInfo>().Verify(v => v.Update(_episodeFile, _series), Times.Never());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_update_media_info_if_token_configured_and_revision_is_newer()
|
||||||
|
{
|
||||||
|
_namingConfig.StandardEpisodeFormat =
|
||||||
|
"{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}.{MediaInfo VideoDynamicRange}";
|
||||||
|
|
||||||
|
GivenMediaInfoModel(schemaRevision: 8);
|
||||||
|
|
||||||
|
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile);
|
||||||
|
|
||||||
|
Mocker.GetMock<IUpdateMediaInfo>().Verify(v => v.Update(_episodeFile, _series), Times.Never());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}.{MediaInfo VideoDynamicRange}")]
|
||||||
|
[TestCase("{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}.{MediaInfo.VideoDynamicRange}")]
|
||||||
|
public void should_use_updated_media_info_if_token_configured_and_revision_is_old(string standardEpisodeFormat)
|
||||||
|
{
|
||||||
|
_namingConfig.StandardEpisodeFormat = standardEpisodeFormat;
|
||||||
|
|
||||||
|
GivenMediaInfoModel(schemaRevision: 3);
|
||||||
|
|
||||||
|
Mocker.GetMock<IUpdateMediaInfo>()
|
||||||
|
.Setup(u => u.Update(_episodeFile, _series))
|
||||||
|
.Callback((EpisodeFile e, Series s) => e.MediaInfo = new MediaInfoModel
|
||||||
|
{
|
||||||
|
VideoCodec = "AVC",
|
||||||
|
AudioFormat = "DTS",
|
||||||
|
AudioChannels = 6,
|
||||||
|
AudioLanguages = "English",
|
||||||
|
Subtitles = "English/Spanish/Italian",
|
||||||
|
VideoBitDepth = 10,
|
||||||
|
VideoColourPrimaries = "BT.2020",
|
||||||
|
VideoTransferCharacteristics = "PQ",
|
||||||
|
SchemaRevision = 5
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile);
|
||||||
|
|
||||||
|
result.Should().EndWith("HDR");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenMediaInfoModel(string videoCodec = "AVC", string audioCodec = "DTS", int audioChannels = 6, int videoBitDepth = 8,
|
||||||
|
string videoColourPrimaries = "", string videoTransferCharacteristics = "", string audioLanguages = "English",
|
||||||
|
string subtitles = "English/Spanish/Italian", int schemaRevision = 5)
|
||||||
|
{
|
||||||
|
_episodeFile.MediaInfo = new MediaInfoModel
|
||||||
|
{
|
||||||
|
VideoCodec = videoCodec,
|
||||||
|
AudioFormat = audioCodec,
|
||||||
|
AudioChannels = audioChannels,
|
||||||
|
AudioLanguages = audioLanguages,
|
||||||
|
Subtitles = subtitles,
|
||||||
|
VideoBitDepth = videoBitDepth,
|
||||||
|
VideoColourPrimaries = videoColourPrimaries,
|
||||||
|
VideoTransferCharacteristics = videoTransferCharacteristics,
|
||||||
|
SchemaRevision = schemaRevision
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using NLog;
|
using NLog;
|
||||||
@ -473,5 +472,27 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
|||||||
// Last token is the default.
|
// Last token is the default.
|
||||||
return tokens.Last();
|
return tokens.Last();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly string[] ValidHdrTransferFunctions = {"PQ", "HLG"};
|
||||||
|
private const string ValidHdrColourPrimaries = "BT.2020";
|
||||||
|
|
||||||
|
public static string FormatVideoDynamicRange(MediaInfoModel mediaInfo)
|
||||||
|
{
|
||||||
|
// assume SDR by default
|
||||||
|
var videoDynamicRange = "";
|
||||||
|
|
||||||
|
if (mediaInfo.VideoBitDepth >= 10 &&
|
||||||
|
!string.IsNullOrEmpty(mediaInfo.VideoColourPrimaries) &&
|
||||||
|
!string.IsNullOrEmpty(mediaInfo.VideoTransferCharacteristics))
|
||||||
|
{
|
||||||
|
if (mediaInfo.VideoColourPrimaries.EqualsIgnoreCase(ValidHdrColourPrimaries) &&
|
||||||
|
ValidHdrTransferFunctions.Any(mediaInfo.VideoTransferCharacteristics.Contains))
|
||||||
|
{
|
||||||
|
videoDynamicRange = "HDR";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return videoDynamicRange;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,12 @@ using NzbDrone.Core.Configuration;
|
|||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles.MediaInfo
|
namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
{
|
{
|
||||||
public class UpdateMediaInfoService : IHandle<SeriesScannedEvent>
|
public interface IUpdateMediaInfo
|
||||||
|
{
|
||||||
|
void Update(EpisodeFile episodeFile, Series series);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UpdateMediaInfoService : IHandle<SeriesScannedEvent>, IUpdateMediaInfo
|
||||||
{
|
{
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly IMediaFileService _mediaFileService;
|
private readonly IMediaFileService _mediaFileService;
|
||||||
@ -31,28 +36,6 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateMediaInfo(Series series, List<EpisodeFile> mediaFiles)
|
|
||||||
{
|
|
||||||
foreach (var mediaFile in mediaFiles)
|
|
||||||
{
|
|
||||||
var path = Path.Combine(series.Path, mediaFile.RelativePath);
|
|
||||||
|
|
||||||
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)
|
public void Handle(SeriesScannedEvent message)
|
||||||
{
|
{
|
||||||
if (!_configService.EnableMediaInfo)
|
if (!_configService.EnableMediaInfo)
|
||||||
@ -62,9 +45,44 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
var allMediaFiles = _mediaFileService.GetFilesBySeries(message.Series.Id);
|
var allMediaFiles = _mediaFileService.GetFilesBySeries(message.Series.Id);
|
||||||
var filteredMediaFiles = allMediaFiles.Where(c => c.MediaInfo == null || c.MediaInfo.SchemaRevision < VideoFileInfoReader.MINIMUM_MEDIA_INFO_SCHEMA_REVISION).ToList();
|
var filteredMediaFiles = allMediaFiles.Where(c =>
|
||||||
|
c.MediaInfo == null ||
|
||||||
|
c.MediaInfo.SchemaRevision < VideoFileInfoReader.MINIMUM_MEDIA_INFO_SCHEMA_REVISION).ToList();
|
||||||
|
|
||||||
UpdateMediaInfo(message.Series, filteredMediaFiles);
|
foreach (var mediaFile in filteredMediaFiles)
|
||||||
|
{
|
||||||
|
UpdateMediaInfo(mediaFile, message.Series);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(EpisodeFile episodeFile, Series series)
|
||||||
|
{
|
||||||
|
if (!_configService.EnableMediaInfo)
|
||||||
|
{
|
||||||
|
_logger.Debug("MediaInfo is disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UpdateMediaInfo(episodeFile, series);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateMediaInfo(EpisodeFile episodeFile, Series series)
|
||||||
|
{
|
||||||
|
var path = Path.Combine(series.Path, episodeFile.RelativePath);
|
||||||
|
|
||||||
|
if (!_diskProvider.FileExists(path))
|
||||||
|
{
|
||||||
|
_logger.Debug("Can't update MediaInfo because '{0}' does not exist", path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var updatedMediaInfo = _videoFileInfoReader.GetMediaInfo(path);
|
||||||
|
|
||||||
|
if (updatedMediaInfo != null)
|
||||||
|
{
|
||||||
|
episodeFile.MediaInfo = updatedMediaInfo;
|
||||||
|
_mediaFileService.Update(episodeFile);
|
||||||
|
_logger.Debug("Updated MediaInfo for '{0}'", path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ namespace NzbDrone.Core.Organizer
|
|||||||
private readonly INamingConfigService _namingConfigService;
|
private readonly INamingConfigService _namingConfigService;
|
||||||
private readonly IQualityDefinitionService _qualityDefinitionService;
|
private readonly IQualityDefinitionService _qualityDefinitionService;
|
||||||
private readonly IPreferredWordService _preferredWordService;
|
private readonly IPreferredWordService _preferredWordService;
|
||||||
|
private readonly IUpdateMediaInfo _mediaInfoUpdater;
|
||||||
private readonly ICached<EpisodeFormat[]> _episodeFormatCache;
|
private readonly ICached<EpisodeFormat[]> _episodeFormatCache;
|
||||||
private readonly ICached<AbsoluteEpisodeFormat[]> _absoluteEpisodeFormatCache;
|
private readonly ICached<AbsoluteEpisodeFormat[]> _absoluteEpisodeFormatCache;
|
||||||
private readonly ICached<bool> _requiresEpisodeTitleCache;
|
private readonly ICached<bool> _requiresEpisodeTitleCache;
|
||||||
@ -81,11 +82,13 @@ namespace NzbDrone.Core.Organizer
|
|||||||
IQualityDefinitionService qualityDefinitionService,
|
IQualityDefinitionService qualityDefinitionService,
|
||||||
ICacheManager cacheManager,
|
ICacheManager cacheManager,
|
||||||
IPreferredWordService preferredWordService,
|
IPreferredWordService preferredWordService,
|
||||||
|
IUpdateMediaInfo mediaInfoUpdater,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
{
|
{
|
||||||
_namingConfigService = namingConfigService;
|
_namingConfigService = namingConfigService;
|
||||||
_qualityDefinitionService = qualityDefinitionService;
|
_qualityDefinitionService = qualityDefinitionService;
|
||||||
_preferredWordService = preferredWordService;
|
_preferredWordService = preferredWordService;
|
||||||
|
_mediaInfoUpdater = mediaInfoUpdater;
|
||||||
_episodeFormatCache = cacheManager.GetCache<EpisodeFormat[]>(GetType(), "episodeFormat");
|
_episodeFormatCache = cacheManager.GetCache<EpisodeFormat[]>(GetType(), "episodeFormat");
|
||||||
_absoluteEpisodeFormatCache = cacheManager.GetCache<AbsoluteEpisodeFormat[]>(GetType(), "absoluteEpisodeFormat");
|
_absoluteEpisodeFormatCache = cacheManager.GetCache<AbsoluteEpisodeFormat[]>(GetType(), "absoluteEpisodeFormat");
|
||||||
_requiresEpisodeTitleCache = cacheManager.GetCache<bool>(GetType(), "requiresEpisodeTitle");
|
_requiresEpisodeTitleCache = cacheManager.GetCache<bool>(GetType(), "requiresEpisodeTitle");
|
||||||
@ -138,6 +141,8 @@ namespace NzbDrone.Core.Organizer
|
|||||||
pattern = AddSeasonEpisodeNumberingTokens(pattern, tokenHandlers, episodes, namingConfig);
|
pattern = AddSeasonEpisodeNumberingTokens(pattern, tokenHandlers, episodes, namingConfig);
|
||||||
pattern = AddAbsoluteNumberingTokens(pattern, tokenHandlers, series, episodes, namingConfig);
|
pattern = AddAbsoluteNumberingTokens(pattern, tokenHandlers, series, episodes, namingConfig);
|
||||||
|
|
||||||
|
UpdateMediaInfoIfNeeded(pattern, episodeFile, series);
|
||||||
|
|
||||||
AddSeriesTokens(tokenHandlers, series);
|
AddSeriesTokens(tokenHandlers, series);
|
||||||
AddIdTokens(tokenHandlers, series);
|
AddIdTokens(tokenHandlers, series);
|
||||||
AddEpisodeTokens(tokenHandlers, episodes);
|
AddEpisodeTokens(tokenHandlers, episodes);
|
||||||
@ -146,6 +151,7 @@ namespace NzbDrone.Core.Organizer
|
|||||||
AddMediaInfoTokens(tokenHandlers, episodeFile);
|
AddMediaInfoTokens(tokenHandlers, episodeFile);
|
||||||
AddPreferredWords(tokenHandlers, series, episodeFile, preferredWords);
|
AddPreferredWords(tokenHandlers, series, episodeFile, preferredWords);
|
||||||
|
|
||||||
|
|
||||||
var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
|
var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
|
||||||
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
|
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
|
||||||
fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty);
|
fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty);
|
||||||
@ -535,6 +541,13 @@ namespace NzbDrone.Core.Organizer
|
|||||||
tokenHandlers["{Quality Real}"] = m => qualityReal;
|
tokenHandlers["{Quality Real}"] = m => qualityReal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const string MediaInfoVideoDynamicRangeToken = "{MediaInfo VideoDynamicRange}";
|
||||||
|
private static readonly IDictionary<string, int> MinimumMediaInfoSchemaRevisions =
|
||||||
|
new Dictionary<string, int>(FileNameBuilderTokenEqualityComparer.Instance)
|
||||||
|
{
|
||||||
|
{MediaInfoVideoDynamicRangeToken, 5}
|
||||||
|
};
|
||||||
|
|
||||||
private void AddMediaInfoTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, EpisodeFile episodeFile)
|
private void AddMediaInfoTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, EpisodeFile episodeFile)
|
||||||
{
|
{
|
||||||
if (episodeFile.MediaInfo == null)
|
if (episodeFile.MediaInfo == null)
|
||||||
@ -549,8 +562,10 @@ namespace NzbDrone.Core.Organizer
|
|||||||
var videoCodec = MediaInfoFormatter.FormatVideoCodec(episodeFile.MediaInfo, sceneName);
|
var videoCodec = MediaInfoFormatter.FormatVideoCodec(episodeFile.MediaInfo, sceneName);
|
||||||
var audioCodec = MediaInfoFormatter.FormatAudioCodec(episodeFile.MediaInfo, sceneName);
|
var audioCodec = MediaInfoFormatter.FormatAudioCodec(episodeFile.MediaInfo, sceneName);
|
||||||
var audioChannels = MediaInfoFormatter.FormatAudioChannels(episodeFile.MediaInfo);
|
var audioChannels = MediaInfoFormatter.FormatAudioChannels(episodeFile.MediaInfo);
|
||||||
|
var audioLanguages = episodeFile.MediaInfo.AudioLanguages ?? string.Empty;
|
||||||
|
var subtitles = episodeFile.MediaInfo.Subtitles ?? string.Empty;
|
||||||
|
|
||||||
var mediaInfoAudioLanguages = GetLanguagesToken(episodeFile.MediaInfo.AudioLanguages);
|
var mediaInfoAudioLanguages = GetLanguagesToken(audioLanguages);
|
||||||
if (!mediaInfoAudioLanguages.IsNullOrWhiteSpace())
|
if (!mediaInfoAudioLanguages.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
mediaInfoAudioLanguages = $"[{mediaInfoAudioLanguages}]";
|
mediaInfoAudioLanguages = $"[{mediaInfoAudioLanguages}]";
|
||||||
@ -561,7 +576,7 @@ namespace NzbDrone.Core.Organizer
|
|||||||
mediaInfoAudioLanguages = string.Empty;
|
mediaInfoAudioLanguages = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var mediaInfoSubtitleLanguages = GetLanguagesToken(episodeFile.MediaInfo.Subtitles);
|
var mediaInfoSubtitleLanguages = GetLanguagesToken(subtitles);
|
||||||
if (!mediaInfoSubtitleLanguages.IsNullOrWhiteSpace())
|
if (!mediaInfoSubtitleLanguages.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
mediaInfoSubtitleLanguages = $"[{mediaInfoSubtitleLanguages}]";
|
mediaInfoSubtitleLanguages = $"[{mediaInfoSubtitleLanguages}]";
|
||||||
@ -586,6 +601,9 @@ namespace NzbDrone.Core.Organizer
|
|||||||
tokenHandlers["{MediaInfo Simple}"] = m => $"{videoCodec} {audioCodec}";
|
tokenHandlers["{MediaInfo Simple}"] = m => $"{videoCodec} {audioCodec}";
|
||||||
|
|
||||||
tokenHandlers["{MediaInfo Full}"] = m => $"{videoCodec} {audioCodec}{mediaInfoAudioLanguages} {mediaInfoSubtitleLanguages}";
|
tokenHandlers["{MediaInfo Full}"] = m => $"{videoCodec} {audioCodec}{mediaInfoAudioLanguages} {mediaInfoSubtitleLanguages}";
|
||||||
|
|
||||||
|
tokenHandlers[MediaInfoVideoDynamicRangeToken] =
|
||||||
|
m => MediaInfoFormatter.FormatVideoDynamicRange(episodeFile.MediaInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddIdTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Series series)
|
private void AddIdTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Series series)
|
||||||
@ -614,7 +632,7 @@ namespace NzbDrone.Core.Organizer
|
|||||||
tokens.Add(item.Trim());
|
tokens.Add(item.Trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
var cultures = System.Globalization.CultureInfo.GetCultures(System.Globalization.CultureTypes.NeutralCultures);
|
var cultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures);
|
||||||
for (int i = 0; i < tokens.Count; i++)
|
for (int i = 0; i < tokens.Count; i++)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -632,6 +650,21 @@ namespace NzbDrone.Core.Organizer
|
|||||||
return string.Join("+", tokens.Distinct());
|
return string.Join("+", tokens.Distinct());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateMediaInfoIfNeeded(string pattern, EpisodeFile episodeFile, Series series)
|
||||||
|
{
|
||||||
|
var schemaRevision = episodeFile.MediaInfo != null ? episodeFile.MediaInfo.SchemaRevision : 0;
|
||||||
|
var matches = TitleRegex.Matches(pattern);
|
||||||
|
|
||||||
|
var shouldUpdateMediaInfo = matches.Cast<Match>()
|
||||||
|
.Select(m => MinimumMediaInfoSchemaRevisions.GetValueOrDefault(m.Value, -1))
|
||||||
|
.Any(r => schemaRevision < r);
|
||||||
|
|
||||||
|
if (shouldUpdateMediaInfo)
|
||||||
|
{
|
||||||
|
_mediaInfoUpdater.Update(episodeFile, series);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private string ReplaceTokens(string pattern, Dictionary<string, Func<TokenMatch, string>> tokenHandlers, NamingConfig namingConfig)
|
private string ReplaceTokens(string pattern, Dictionary<string, Func<TokenMatch, string>> tokenHandlers, NamingConfig namingConfig)
|
||||||
{
|
{
|
||||||
return TitleRegex.Replace(pattern, match => ReplaceToken(match, tokenHandlers, namingConfig));
|
return TitleRegex.Replace(pattern, match => ReplaceToken(match, tokenHandlers, namingConfig));
|
||||||
|
Loading…
Reference in New Issue
Block a user