diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 105ca7ea3..4907bfd1e 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -241,6 +241,7 @@ stages:
- bash: ./build.sh --packages --enable-bsd
displayName: Create Packages
- bash: |
+ find . -name "ffprobe" -exec chmod a+x {} \;
find . -name "Radarr" -exec chmod a+x {} \;
find . -name "Radarr.Update" -exec chmod a+x {} \;
displayName: Set executable bits
@@ -417,16 +418,13 @@ stages:
buildType: 'current'
artifactName: '$(testName)Tests'
targetPath: $(testsFolder)
- - bash: |
- wget https://mediaarea.net/repo/deb/repo-mediaarea_1.0-11_all.deb
- sudo dpkg -i repo-mediaarea_1.0-11_all.deb
- sudo apt-get update
- sudo apt-get install -y --allow-unauthenticated libmediainfo-dev libmediainfo0v5 mediainfo
- displayName: Install mediainfo
- condition: and(succeeded(), eq(variables['testName'], 'LinuxCore'))
- powershell: Set-Service SCardSvr -StartupType Manual
displayName: Enable Windows Test Service
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
+ - bash: |
+ chmod a+x _tests/ffprobe
+ displayName: Make ffprobe Executable
+ condition: and(succeeded(), ne(variables['osName'], 'Windows'))
- bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \;
displayName: Make Test Dummy Executable
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
@@ -474,6 +472,9 @@ stages:
buildType: 'current'
artifactName: $(artifactName)
targetPath: $(testsFolder)
+ - bash: |
+ chmod a+x _tests/ffprobe
+ displayName: Make ffprobe Executable
- bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \;
displayName: Make Test Dummy Executable
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
diff --git a/src/NuGet.config b/src/NuGet.config
index 19fea7384..2e32ec8bf 100644
--- a/src/NuGet.config
+++ b/src/NuGet.config
@@ -7,5 +7,6 @@
+
diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioChannelsFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioChannelsFixture.cs
deleted file mode 100644
index 3cd71c247..000000000
--- a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioChannelsFixture.cs
+++ /dev/null
@@ -1,321 +0,0 @@
-using System.Globalization;
-using System.Threading;
-using FluentAssertions;
-using NUnit.Framework;
-using NzbDrone.Core.MediaFiles.MediaInfo;
-using NzbDrone.Test.Common;
-
-namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
-{
- [TestFixture]
- public class FormatAudioChannelsFixture : TestBase
- {
- [Test]
- public void should_subtract_one_from_AudioChannels_as_total_channels_if_LFE_in_AudioChannelPositionsText()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioChannelsContainer = 6,
- AudioChannelPositions = null,
- AudioChannelPositionsTextContainer = "Front: L C R, Side: L R, LFE"
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
- }
-
- [Test]
- public void should_use_AudioChannels_as_total_channels_if_LFE_not_in_AudioChannelPositionsText()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioChannelsContainer = 2,
- AudioChannelPositions = null,
- AudioChannelPositionsTextContainer = "Front: L R"
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
- }
-
- [Test]
- public void should_return_0_if_schema_revision_is_less_than_3_and_other_properties_are_null()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioChannelsContainer = 2,
- AudioChannelPositions = null,
- AudioChannelPositionsTextContainer = null,
- SchemaRevision = 2
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(0);
- }
-
- [Test]
- public void should_use_AudioChannels_if_schema_revision_is_3_and_other_properties_are_null()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioChannelsContainer = 2,
- AudioChannelPositions = null,
- AudioChannelPositionsTextContainer = null,
- SchemaRevision = 3
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
- }
-
- [Test]
- public void should_use_AudioChannels_if_schema_revision_is_3_and_AudioChannelPositions_is_0()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioFormat = "FLAC",
- AudioChannelsContainer = 6,
- AudioChannelPositions = "0/0/0",
- AudioChannelPositionsTextContainer = null,
- SchemaRevision = 3
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
- }
-
- [Test]
- public void should_sum_AudioChannelPositions()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioChannelsContainer = 2,
- AudioChannelPositions = "2/0/0",
- AudioChannelPositionsTextContainer = null,
- SchemaRevision = 3
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
- }
-
- [Test]
- public void should_sum_AudioChannelPositions_including_decimal()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioChannelsContainer = 2,
- AudioChannelPositions = "3/2/0.1",
- AudioChannelPositionsTextContainer = null,
- SchemaRevision = 3
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
- }
-
- [Test]
- public void should_format_8_channel_object_based_as_71_if_dtsx()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioChannelsContainer = 8,
- AudioChannelsStream = 0,
- AudioFormat = "DTS",
- AudioAdditionalFeatures = "XLL X",
- AudioChannelPositions = "Object Based",
- AudioChannelPositionsTextContainer = "Object Based",
- AudioChannelPositionsTextStream = "Object Based",
- SchemaRevision = 3
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
- }
-
- [Test]
- public void should_format_8_channel_blank_as_71_if_dtsx()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioChannelsContainer = 8,
- AudioChannelsStream = 0,
- AudioFormat = "DTS",
- AudioAdditionalFeatures = "XLL X",
- AudioChannelPositions = "",
- AudioChannelPositionsTextContainer = "",
- AudioChannelPositionsTextStream = "Object Based",
- SchemaRevision = 3
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
- }
-
- [Test]
- public void should_format_6_channel_zero_as_51_if_flac()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioFormat = "FLAC",
- AudioChannelsContainer = 6,
- AudioChannelPositions = "0/0/0",
- AudioChannelPositionsTextContainer = null,
- SchemaRevision = 3
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
- }
-
- [Test]
- public void should_ignore_culture_on_channel_summary()
- {
- Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");
-
- var mediaInfoModel = new MediaInfoModel
- {
- AudioChannelsContainer = 2,
- AudioChannelPositions = "3/2/0.1",
- AudioChannelPositionsTextContainer = null,
- SchemaRevision = 3
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
- }
-
- [Test]
- public void should_handle_AudioChannelPositions_three_digits()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioChannelsContainer = 2,
- AudioChannelPositions = "3/2/0.2.1",
- AudioChannelPositionsTextContainer = null,
- SchemaRevision = 3
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
- }
-
- [Test]
- public void should_cleanup_extraneous_text_from_AudioChannelPositions()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioChannelsContainer = 2,
- AudioChannelPositions = "Object Based / 3/2/2.1",
- AudioChannelPositionsTextContainer = null,
- SchemaRevision = 3
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
- }
-
- [Test]
- public void should_skip_empty_groups_in_AudioChannelPositions()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioChannelsContainer = 2,
- AudioChannelPositions = " / 2/0/0.0",
- AudioChannelPositionsTextContainer = null,
- SchemaRevision = 3
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
- }
-
- [Test]
- public void should_sum_first_series_of_numbers_from_AudioChannelPositions()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioChannelsContainer = 2,
- AudioChannelPositions = "3/2/2.1 / 3/2/2.1",
- AudioChannelPositionsTextContainer = null,
- SchemaRevision = 3
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
- }
-
- [Test]
- public void should_sum_first_series_of_numbers_from_AudioChannelPositions_with_three_digits()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioChannelsContainer = 2,
- AudioChannelPositions = "3/2/0.2.1 / 3/2/0.1",
- AudioChannelPositionsTextContainer = null,
- SchemaRevision = 3
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
- }
-
- [Test]
- public void should_sum_dual_mono_representation_AudioChannelPositions()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioChannelsContainer = 2,
- AudioChannelPositions = "1+1",
- AudioChannelPositionsTextContainer = null,
- SchemaRevision = 3
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2.0m);
- }
-
- [Test]
- public void should_use_AudioChannelPositionText_when_AudioChannelChannelPosition_is_invalid()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioChannelsContainer = 6,
- AudioChannelPositions = "15 objects",
- AudioChannelPositionsTextContainer = "15 objects / Front: L C R, Side: L R, LFE",
- SchemaRevision = 3
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
- }
-
- [Test]
- public void should_remove_atmos_objects_from_AudioChannelPostions()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioChannelsContainer = 2,
- AudioChannelPositions = "15 objects / 3/2.1",
- AudioChannelPositionsTextContainer = null,
- SchemaRevision = 3
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
- }
-
- [Test]
- public void should_use_audio_stream_text_when_exists()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioChannelsContainer = 6,
- AudioChannelsStream = 8,
- AudioChannelPositions = null,
- AudioChannelPositionsTextContainer = null,
- AudioChannelPositionsTextStream = "Front: L C R, Side: L R, Back: L R, LFE",
- SchemaRevision = 6
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
- }
-
- [Test]
- public void should_use_audio_stream_channels_when_exists()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioChannelsContainer = 6,
- AudioChannelsStream = 8,
- AudioChannelPositions = null,
- AudioChannelPositionsTextContainer = null,
- AudioChannelPositionsTextStream = null,
- SchemaRevision = 6
- };
-
- MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(8m);
- }
- }
-}
diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioCodecFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioCodecFixture.cs
index 09156a025..6e0615613 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioCodecFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioCodecFixture.cs
@@ -10,45 +10,31 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
{
private static string sceneName = "My.Series.S01E01-Sonarr";
- [TestCase("AC-3", "AC3")]
- [TestCase("E-AC-3", "EAC3")]
- [TestCase("MPEG Audio", "MPEG Audio")]
- [TestCase("DTS", "DTS")]
- public void should_format_audio_format_legacy(string audioFormat, string expectedFormat)
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioFormat = audioFormat
- };
+ [TestCase("mp2, , ", "droned.s01e03.swedish.720p.hdtv.x264-prince", "MP2")]
+ [TestCase("vorbis, , ", "DB Super HDTV", "Vorbis")]
+ [TestCase("pcm_s16le, , ", "DW DVDRip XviD-idTV, ", "PCM")]
+ [TestCase("truehd, , ", "", "TrueHD")]
+ [TestCase("truehd, , ", "TrueHD", "TrueHD")]
+ [TestCase("truehd, thd+, ", "Atmos", "TrueHD Atmos")]
+ [TestCase("truehd, thd+, ", "TrueHD.Atmos.7.1", "TrueHD Atmos")]
+ [TestCase("truehd, thd+, ", "", "TrueHD Atmos")]
+ [TestCase("wmav1, , ", "Droned.wmv", "WMA")]
+ [TestCase("wmav2, , ", "B.N.S04E18.720p.WEB-DL", "WMA")]
+ [TestCase("opus, , ", "Roadkill Ep3x11 - YouTube.webm", "Opus")]
+ [TestCase("mp3, , ", "climbing.mp4", "MP3")]
+ [TestCase("dts, , DTS-HD MA", "DTS-HD.MA", "DTS-HD MA")]
+ [TestCase("dts, , DTS:X", "DTS-X", "DTS-X")]
+ [TestCase("dts, , DTS-HD MA", "DTS-HD.MA", "DTS-HD MA")]
+ [TestCase("dts, , DTS-ES", "DTS-ES", "DTS-ES")]
+ [TestCase("dts, , DTS-ES", "DTS", "DTS-ES")]
+ [TestCase("dts, , DTS-HD HRA", "DTSHD-HRA", "DTS-HD HRA")]
+ [TestCase("dts, , DTS", "DTS", "DTS")]
+ [TestCase("eac3, ec+3, ", "EAC3.Atmos", "EAC3 Atmos")]
+ [TestCase("eac3, , ", "DDP5.1", "EAC3")]
+ [TestCase("ac3, , ", "DD5.1", "AC3")]
+ [TestCase("adpcm_ima_qt, , ", "Custom?", "PCM")]
+ [TestCase("adpcm_ms, , ", "Custom", "PCM")]
- MediaInfoFormatter.FormatAudioCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
- }
-
- [TestCase("MPEG Audio, A_MPEG/L2, , , ", "droned.s01e03.swedish.720p.hdtv.x264-prince", "MP2")]
- [TestCase("Vorbis, A_VORBIS, , Xiph.Org libVorbis I 20101101 (Schaufenugget), ", "DB Super HDTV", "Vorbis")]
- [TestCase("PCM, 1, , , ", "DW DVDRip XviD-idTV, ", "PCM")] // Dubbed most likely
- [TestCase("TrueHD, A_TRUEHD, , , ", "", "TrueHD")]
- [TestCase("MLP FBA, A_TRUEHD, , , ", "TrueHD", "TrueHD")]
- [TestCase("MLP FBA, A_TRUEHD, , , 16-ch", "Atmos", "TrueHD Atmos")]
- [TestCase("Atmos / TrueHD, A_TRUEHD, , , ", "TrueHD.Atmos.7.1", "TrueHD Atmos")]
- [TestCase("Atmos / TrueHD / AC-3, 131, , , ", "", "TrueHD Atmos")]
- [TestCase("WMA, 161, , , ", "Droned.wmv", "WMA")]
- [TestCase("WMA, 162, Pro, , ", "B.N.S04E18.720p.WEB-DL", "WMA")]
- [TestCase("Opus, A_OPUS, , , ", "Roadkill Ep3x11 - YouTube.webm", "Opus")]
- [TestCase("mp3 , 0, , , ", "climbing.mp4", "MP3")]
- [TestCase("DTS, A_DTS, , , XLL", "DTS-HD.MA", "DTS-HD MA")]
- [TestCase("DTS, A_DTS, , , XLL X", "DTS-X", "DTS-X")]
- [TestCase("DTS, A_DTS, , , ES XLL", "DTS-HD.MA", "DTS-HD MA")]
- [TestCase("DTS, A_DTS, , , ES", "DTS-ES", "DTS-ES")]
- [TestCase("DTS, A_DTS, , , ES XXCH", "DTS", "DTS-ES")]
- [TestCase("DTS, A_DTS, , , XBR", "DTSHD-HRA", "DTS-HD HRA")]
- [TestCase("DTS, A_DTS, , , DTS", "DTS", "DTS")]
- [TestCase("E-AC-3, A_EAC3, , , JOC", "EAC3.Atmos", "EAC3 Atmos")]
- [TestCase("E-AC-3, A_EAC3, , , ", "DDP5.1", "EAC3")]
- [TestCase("AC-3, A_AC3, , , ", "DD5.1", "AC3")]
- [TestCase("A_QUICKTIME, A_QUICKTIME, , , ", "", "")]
- [TestCase("ADPCM, 2, , , ", "Custom?", "PCM")]
- [TestCase("ADPCM, ima4, , , ", "Custom", "PCM")]
public void should_format_audio_format(string audioFormatPack, string sceneName, string expectedFormat)
{
var split = audioFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
@@ -56,26 +42,12 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
{
AudioFormat = split[0],
AudioCodecID = split[1],
- AudioProfile = split[2],
- AudioCodecLibrary = split[3],
- AudioAdditionalFeatures = split[4]
+ AudioProfile = split[2]
};
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
}
- [Test]
- public void should_return_MP3_for_MPEG_Audio_with_Layer_3_for_the_profile()
- {
- var mediaInfoModel = new MediaInfoModel
- {
- AudioFormat = "MPEG Audio",
- AudioProfile = "Layer 3"
- };
-
- MediaInfoFormatter.FormatAudioCodec(mediaInfoModel, sceneName).Should().Be("MP3");
- }
-
[Test]
public void should_return_AudioFormat_by_default()
{
diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoCodecFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoCodecFixture.cs
index dd0d81d42..0687e44d2 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoCodecFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoCodecFixture.cs
@@ -8,94 +8,55 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
[TestFixture]
public class FormatVideoCodecFixture : TestBase
{
- [TestCase("AVC", null, "x264")]
- [TestCase("AVC", "source.title.x264.720p-Sonarr", "x264")]
- [TestCase("AVC", "source.title.h264.720p-Sonarr", "h264")]
- [TestCase("V_MPEGH/ISO/HEVC", null, "x265")]
- [TestCase("V_MPEGH/ISO/HEVC", "source.title.x265.720p-Sonarr", "x265")]
- [TestCase("V_MPEGH/ISO/HEVC", "source.title.h265.720p-Sonarr", "h265")]
- [TestCase("MPEG-2 Video", null, "MPEG2")]
- public void should_format_video_codec_with_source_title_legacy(string videoCodec, string sceneName, string expectedFormat)
- {
- var mediaInfoModel = new MediaInfoModel
- {
- VideoCodec = videoCodec
- };
-
- MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
- }
-
- [TestCase("MPEG Video, 2, Main@High, ", "Droned.S01E02.1080i.HDTV.DD5.1.MPEG2-NTb", "MPEG2")]
- [TestCase("MPEG Video, V_MPEG2, Main@High, ", "", "MPEG2")]
- [TestCase("MPEG Video, , , ", "The.Simpsons.S13E04.INTERNAL-ANiVCD.mpg", "MPEG")]
- [TestCase("VC-1, WVC1, Advanced@L4, ", "B.N.S04E18.720p.WEB-DL", "VC1")]
- [TestCase("VC-1, V_MS/VFW/FOURCC / WVC1, Advanced@L3, ", "", "VC1")]
- [TestCase("VC-1, WMV3, MP@LL, ", "It's Always Sunny S07E13 The Gang's RevengeHDTV.XviD-2HD.avi", "VC1")]
- [TestCase("V.MPEG4/ISO/AVC, V.MPEG4/ISO/AVC, , ", "pd.2015.S03E08.720p.iP.WEBRip.AAC2.0.H264-BTW", "h264")]
- [TestCase("AVC / AVC, V_MPEG4/ISO/AVC, High@L4, ", "Resistance.2019.S01E03.1080p.RTE.WEB-DL.AAC2.0.x264-RTN", "x264")]
- [TestCase("WMV1, WMV1, , ", "Droned.wmv", "WMV")]
- [TestCase("WMV2, WMV2, , ", "Droned.wmv", "WMV")]
- [TestCase("xvid, xvid, , ", "", "XviD")]
- [TestCase("div3, div3, , ", "spsm.dvdrip.divx.avi'.", "DivX")]
- [TestCase("VP6, 4, , ", "Top Gear - S12E01 - Lorries - SD TV.flv", "VP6")]
- [TestCase("VP7, VP70, General, ", "Sweet Seymour.avi", "VP7")]
- [TestCase("VP8, V_VP8, , ", "Dick.mkv", "VP8")]
- [TestCase("VP9, V_VP9, , ", "Roadkill Ep3x11 - YouTube.webm", "VP9")]
- [TestCase("x264, x264, , ", "Ghost Advent - S04E05 - Stanley Hotel SDTV.avi", "x264")]
- [TestCase("V_MPEGH/ISO/HEVC, V_MPEGH/ISO/HEVC, , ", "The BBT S11E12 The Matrimonial Metric 1080p 10bit AMZN WEB-DL", "h265")]
- [TestCase("MPEG-4 Visual, 20, Simple@L1, Lavc52.29.0", "Will.And.Grace.S08E14.WS.DVDrip.XviD.I.Love.L.Gay-Obfuscated", "XviD")]
- [TestCase("MPEG-4 Visual, 20, Advanced Simple@L5, XviD0046", "", "XviD")]
- [TestCase("MPEG-4 Visual, 20, , ", "", "")]
- [TestCase("MPEG-4 Visual, mp4v-20, Simple@L1, Lavc57.48.101", "", "")]
- [TestCase("mp4v, mp4v, , ", "American.Chopper.S06E07.Mountain.Creek.Bike.DSR.XviD-KRS", "XviD")]
- [TestCase("V_QUICKTIME, V_QUICKTIME, , ", "Custom", "")]
- [TestCase("MPEG-4 Visual, FMP4, , ", "", "")]
- [TestCase("MPEG-4 Visual, MP42, , ", "", "")]
- [TestCase("mp43, V_MS/VFW/FOURCC / mp43, , ", "Bubble.Guppies.S01E13.480p.WEB-DL.H.264-BTN-Custom", "")]
+ [TestCase("mpeg2video, ", "Droned.S01E02.1080i.HDTV.DD5.1.MPEG2-NTb", "MPEG2")]
+ [TestCase("mpeg2video, ", "", "MPEG2")]
+ [TestCase("mpeg1video, ", "The.Simpsons.S13E04.INTERNAL-ANiVCD.mpg", "MPEG")]
+ [TestCase("vc1, WVC1", "B.N.S04E18.720p.WEB-DL", "VC1")]
+ [TestCase("vc1, V_MS/VFW/FOURCC/WVC1", "", "VC1")]
+ [TestCase("vc1, WMV3", "It's Always Sunny S07E13 The Gang's RevengeHDTV.XviD-2HD.avi", "VC1")]
+ [TestCase("h264, V.MPEG4/ISO/AVC", "pd.2015.S03E08.720p.iP.WEBRip.AAC2.0.H264-BTW", "h264")]
+ [TestCase("h264, V_MPEG4/ISO/AVC", "Resistance.2019.S01E03.1080p.RTE.WEB-DL.AAC2.0.x264-RTN", "x264")]
+ [TestCase("wmv1, WMV1", "Droned.wmv", "WMV")]
+ [TestCase("wmv2, WMV2", "Droned.wmv", "WMV")]
+ [TestCase("mpeg4, XVID", "", "XviD")]
+ [TestCase("mpeg4, DIV3", "spsm.dvdrip.divx.avi'.", "DivX")]
+ [TestCase("vp6, 4", "Top Gear - S12E01 - Lorries - SD TV.flv", "VP6")]
+ [TestCase("vp7, VP70", "Sweet Seymour.avi", "VP7")]
+ [TestCase("vp8, V_VP8", "Dick.mkv", "VP8")]
+ [TestCase("vp9, V_VP9", "Roadkill Ep3x11 - YouTube.webm", "VP9")]
+ [TestCase("h264, x264", "Ghost Advent - S04E05 - Stanley Hotel SDTV.avi", "x264")]
+ [TestCase("hevc, V_MPEGH/ISO/HEVC", "The BBT S11E12 The Matrimonial Metric 1080p 10bit AMZN WEB-DL", "h265")]
+ [TestCase("mpeg4, mp4v-20", "", "")]
+ [TestCase("mpeg4, XVID", "American.Chopper.S06E07.Mountain.Creek.Bike.DSR.XviD-KRS", "XviD")]
+ [TestCase("rpza, V_QUICKTIME", "Custom", "")]
+ [TestCase("mpeg4, FMP4", "", "")]
+ [TestCase("mpeg4, MP42", "", "")]
+ [TestCase("mpeg4, mp43", "Bubble.Guppies.S01E13.480p.WEB-DL.H.264-BTN-Custom", "")]
public void should_format_video_format(string videoFormatPack, string sceneName, string expectedFormat)
{
var split = videoFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
var mediaInfoModel = new MediaInfoModel
{
VideoFormat = split[0],
- VideoCodecID = split[1],
- VideoProfile = split[2],
- VideoCodecLibrary = split[3]
+ VideoCodecID = split[1]
};
MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
}
- [TestCase("AVC, AVC, , x264", "Some.Video.S01E01.h264", "x264")] // Force mediainfo tag
- [TestCase("HEVC, HEVC, , x265", "Some.Video.S01E01.h265", "x265")] // Force mediainfo tag
- [TestCase("AVC, AVC, , ", "Some.Video.S01E01.x264", "x264")] // Not seen in practice, but honor tag if otherwise unknown
- [TestCase("HEVC, HEVC, , ", "Some.Video.S01E01.x265", "x265")] // Not seen in practice, but honor tag if otherwise unknown
- [TestCase("AVC, AVC, , ", "Some.Video.S01E01", "h264")] // Default value
- [TestCase("HEVC, HEVC, , ", "Some.Video.S01E01", "h265")] // Default value
+ [TestCase("h264, x264", "Some.Video.S01E01.h264", "x264")] // Force mediainfo tag
+ [TestCase("hevc, x265", "Some.Video.S01E01.h265", "x265")] // Force mediainfo tag
+ [TestCase("h264, ", "Some.Video.S01E01.x264", "x264")] // Not seen in practice, but honor tag if otherwise unknown
+ [TestCase("hevc, ", "Some.Video.S01E01.x265", "x265")] // Not seen in practice, but honor tag if otherwise unknown
+ [TestCase("h264, ", "Some.Video.S01E01", "h264")] // Default value
+ [TestCase("hevc, ", "Some.Video.S01E01", "h265")] // Default value
public void should_format_video_format_fallbacks(string videoFormatPack, string sceneName, string expectedFormat)
{
var split = videoFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
var mediaInfoModel = new MediaInfoModel
{
VideoFormat = split[0],
- VideoCodecID = split[1],
- VideoProfile = split[2],
- VideoCodecLibrary = split[3]
- };
-
- MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
- }
-
- [TestCase("MPEG-4 Visual, 20, , Intel(R) MPEG-4 encoder based on Intel(R) IPP 6.1 build 137.20[6.1.137.763]", "", "")]
- public void should_warn_on_unknown_video_format(string videoFormatPack, string sceneName, string expectedFormat)
- {
- var split = videoFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
- var mediaInfoModel = new MediaInfoModel
- {
- VideoFormat = split[0],
- VideoCodecID = split[1],
- VideoProfile = split[2],
- VideoCodecLibrary = split[3]
+ VideoCodecID = split[1]
};
MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoDynamicRangeFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoDynamicRangeFixture.cs
index 897065906..ab3b620bf 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoDynamicRangeFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoDynamicRangeFixture.cs
@@ -1,4 +1,4 @@
-using FluentAssertions;
+using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Test.Common;
@@ -8,28 +8,18 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
[TestFixture]
public class FormatVideoDynamicRangeFixture : TestBase
{
- [TestCase(8, "", "", "", "", "")]
- [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")]
- [TestCase(10, "", "", "Dolby Vision", "", "HDR")]
- [TestCase(10, "", "", "SMPTE ST 2086", "HDR10", "HDR")]
- [TestCase(8, "", "", "Dolby Vision", "", "HDR")]
- [TestCase(8, "", "", "SMPTE ST 2086", "HDR10", "HDR")]
- [TestCase(10, "BT.2020", "PQ", "Dolby Vision / SMPTE ST 2086", "Blu-ray / HDR10", "HDR")]
- public void should_format_video_dynamic_range(int bitDepth, string colourPrimaries, string transferCharacteristics, string hdrFormat, string hdrFormatCompatibility, string expectedVideoDynamicRange)
+ [TestCase(HdrFormat.None, "")]
+ [TestCase(HdrFormat.Hlg10, "HDR")]
+ [TestCase(HdrFormat.Pq10, "HDR")]
+ [TestCase(HdrFormat.Hdr10, "HDR")]
+ [TestCase(HdrFormat.Hdr10Plus, "HDR")]
+ [TestCase(HdrFormat.DolbyVision, "HDR")]
+ public void should_format_video_dynamic_range(HdrFormat format, string expectedVideoDynamicRange)
{
var mediaInfo = new MediaInfoModel
{
- VideoBitDepth = bitDepth,
- VideoColourPrimaries = colourPrimaries,
- VideoTransferCharacteristics = transferCharacteristics,
- VideoHdrFormat = hdrFormat,
- VideoHdrFormatCompatibility = hdrFormatCompatibility,
- SchemaRevision = 7
+ VideoHdrFormat = format,
+ SchemaRevision = 8
};
MediaInfoFormatter.FormatVideoDynamicRange(mediaInfo).Should().Be(expectedVideoDynamicRange);
diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs
index 946efbf62..3fb1e253e 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs
@@ -1,8 +1,13 @@
+using System;
using System.IO;
+using System.Linq;
+using System.Reflection;
+using FFMpegCore;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
+using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common.Categories;
@@ -40,33 +45,26 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
var info = Subject.GetMediaInfo(path);
- info.VideoCodec.Should().BeNull();
- info.VideoFormat.Should().Be("AVC");
+ info.VideoFormat.Should().Be("h264");
info.VideoCodecID.Should().Be("avc1");
- info.VideoProfile.Should().Be("Baseline@L2.1");
- info.VideoCodecLibrary.Should().Be("");
- info.AudioFormat.Should().Be("AAC");
- info.AudioCodecID.Should().BeOneOf("40", "mp4a-40-2");
- info.AudioProfile.Should().BeOneOf("", "LC");
- info.AudioCodecLibrary.Should().Be("");
- info.AudioBitrate.Should().Be(128000);
- info.AudioChannelsContainer.Should().Be(2);
- info.AudioChannelsStream.Should().Be(0);
- info.AudioChannelPositionsTextContainer.Should().Be("Front: L R");
- info.AudioChannelPositionsTextStream.Should().Be("");
- info.AudioLanguages.Should().Be("English");
+ info.VideoProfile.Should().Be("Constrained Baseline");
+ info.AudioFormat.Should().Be("aac");
+ info.AudioCodecID.Should().Be("mp4a");
+ info.AudioProfile.Should().Be("LC");
+ info.AudioBitrate.Should().Be(125488);
+ info.AudioChannels.Should().Be(2);
+ info.AudioChannelPositions.Should().Be("stereo");
+ info.AudioLanguages.Should().BeEquivalentTo("eng");
info.Height.Should().Be(320);
info.RunTime.Seconds.Should().Be(10);
info.ScanType.Should().Be("Progressive");
- info.Subtitles.Should().Be("");
- info.VideoBitrate.Should().Be(193329);
+ info.Subtitles.Should().BeEmpty();
+ info.VideoBitrate.Should().Be(193328);
info.VideoFps.Should().Be(24);
info.Width.Should().Be(480);
- info.VideoColourPrimaries.Should().Be("BT.601 NTSC");
- info.VideoTransferCharacteristics.Should().Be("BT.709");
- info.AudioAdditionalFeatures.Should().BeOneOf("", "LC");
- info.VideoHdrFormat.Should().BeEmpty();
- info.VideoHdrFormatCompatibility.Should().BeEmpty();
+ info.VideoBitDepth.Should().Be(8);
+ info.VideoColourPrimaries.Should().Be("smpte170m");
+ info.VideoTransferCharacteristics.Should().Be("bt709");
}
[Test]
@@ -83,45 +81,48 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
var info = Subject.GetMediaInfo(path);
- info.VideoCodec.Should().BeNull();
- info.VideoFormat.Should().Be("AVC");
+ info.VideoFormat.Should().Be("h264");
info.VideoCodecID.Should().Be("avc1");
- info.VideoProfile.Should().Be("Baseline@L2.1");
- info.VideoCodecLibrary.Should().Be("");
- info.AudioFormat.Should().Be("AAC");
- info.AudioCodecID.Should().BeOneOf("40", "mp4a-40-2");
- info.AudioProfile.Should().BeOneOf("", "LC");
- info.AudioCodecLibrary.Should().Be("");
- info.AudioBitrate.Should().Be(128000);
- info.AudioChannelsContainer.Should().Be(2);
- info.AudioChannelsStream.Should().Be(0);
- info.AudioChannelPositionsTextContainer.Should().Be("Front: L R");
- info.AudioChannelPositionsTextStream.Should().Be("");
- info.AudioLanguages.Should().Be("English");
+ info.VideoProfile.Should().Be("Constrained Baseline");
+ info.AudioFormat.Should().Be("aac");
+ info.AudioCodecID.Should().Be("mp4a");
+ info.AudioProfile.Should().Be("LC");
+ info.AudioBitrate.Should().Be(125488);
+ info.AudioChannels.Should().Be(2);
+ info.AudioChannelPositions.Should().Be("stereo");
+ info.AudioLanguages.Should().BeEquivalentTo("eng");
info.Height.Should().Be(320);
info.RunTime.Seconds.Should().Be(10);
info.ScanType.Should().Be("Progressive");
- info.Subtitles.Should().Be("");
- info.VideoBitrate.Should().Be(193329);
+ info.Subtitles.Should().BeEmpty();
+ info.VideoBitrate.Should().Be(193328);
info.VideoFps.Should().Be(24);
info.Width.Should().Be(480);
- info.VideoColourPrimaries.Should().Be("BT.601 NTSC");
- info.VideoTransferCharacteristics.Should().Be("BT.709");
- info.AudioAdditionalFeatures.Should().BeOneOf("", "LC");
- info.VideoHdrFormat.Should().BeEmpty();
- info.VideoHdrFormatCompatibility.Should().BeEmpty();
+ info.VideoColourPrimaries.Should().Be("smpte170m");
+ info.VideoTransferCharacteristics.Should().Be("bt709");
}
- [Test]
- public void should_dispose_file_after_scanning_mediainfo()
+ [TestCase(8, "", "", "", HdrFormat.None)]
+ [TestCase(10, "", "", "", HdrFormat.None)]
+ [TestCase(10, "bt709", "bt709", "", HdrFormat.None)]
+ [TestCase(8, "bt2020", "smpte2084", "", HdrFormat.None)]
+ [TestCase(10, "bt2020", "bt2020-10", "", HdrFormat.Hlg10)]
+ [TestCase(10, "bt2020", "arib-std-b67", "", HdrFormat.Hlg10)]
+ [TestCase(10, "bt2020", "smpte2084", "", HdrFormat.Pq10)]
+ [TestCase(10, "bt2020", "smpte2084", "FFMpegCore.SideData", HdrFormat.Pq10)]
+ [TestCase(10, "bt2020", "smpte2084", "FFMpegCore.MasteringDisplayMetadata", HdrFormat.Hdr10)]
+ [TestCase(10, "bt2020", "smpte2084", "FFMpegCore.ContentLightLevelMetadata", HdrFormat.Hdr10)]
+ [TestCase(10, "bt2020", "smpte2084", "FFMpegCore.HdrDynamicMetadataSpmte2094", HdrFormat.Hdr10Plus)]
+ [TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", HdrFormat.DolbyVision)]
+ public void should_detect_hdr_correctly(int bitDepth, string colourPrimaries, string transferFunction, string sideDataTypes, HdrFormat expected)
{
- var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "H264_sample.mp4");
+ var assembly = Assembly.GetAssembly(typeof(FFProbe));
+ var types = sideDataTypes.Split(",").Select(x => x.Trim()).ToList();
+ var sideData = types.Where(x => x.IsNotNullOrWhiteSpace()).Select(x => assembly.CreateInstance(x)).Cast().ToList();
- var info = Subject.GetMediaInfo(path);
+ var result = VideoFileInfoReader.GetHdrFormat(bitDepth, colourPrimaries, transferFunction, sideData);
- var stream = new FileStream(path, FileMode.Open, FileAccess.Write);
-
- stream.Close();
+ result.Should().Be(expected);
}
}
}
diff --git a/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromMediaInfoFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromMediaInfoFixture.cs
index ef386893e..5e098b7ab 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromMediaInfoFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromMediaInfoFixture.cs
@@ -1,3 +1,4 @@
+using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
@@ -25,7 +26,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators.Augm
public void should_return_language_for_single_known_language()
{
var mediaInfo = Builder.CreateNew()
- .With(m => m.AudioLanguages = "English")
+ .With(m => m.AudioLanguages = new List { "eng" })
.Build();
var localMovie = Builder.CreateNew()
@@ -42,7 +43,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators.Augm
public void should_only_return_one_when_language_duplicated()
{
var mediaInfo = Builder.CreateNew()
- .With(m => m.AudioLanguages = "English / English")
+ .With(m => m.AudioLanguages = new List { "eng", "eng" })
.Build();
var localMovie = Builder.CreateNew()
@@ -59,7 +60,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators.Augm
public void should_return_null_if_all_unknown()
{
var mediaInfo = Builder.CreateNew()
- .With(m => m.AudioLanguages = "Pirate / Pirate")
+ .With(m => m.AudioLanguages = new List { "pirate", "pirate" })
.Build();
var localMovie = Builder.CreateNew()
@@ -75,7 +76,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators.Augm
public void should_return_known_languages_only()
{
var mediaInfo = Builder.CreateNew()
- .With(m => m.AudioLanguages = "English / Pirate")
+ .With(m => m.AudioLanguages = new List { "eng", "pirate" })
.Build();
var localMovie = Builder.CreateNew()
@@ -92,7 +93,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators.Augm
public void should_return_multiple_known_languages()
{
var mediaInfo = Builder.CreateNew()
- .With(m => m.AudioLanguages = "English / German")
+ .With(m => m.AudioLanguages = new List { "eng", "ger" })
.Build();
var localMovie = Builder.CreateNew()
diff --git a/src/NzbDrone.Core.Test/NotificationTests/TraktTests/TraktServiceFixture.cs b/src/NzbDrone.Core.Test/NotificationTests/TraktTests/TraktServiceFixture.cs
index a6503b5ec..74f6a2f56 100644
--- a/src/NzbDrone.Core.Test/NotificationTests/TraktTests/TraktServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/NotificationTests/TraktTests/TraktServiceFixture.cs
@@ -65,7 +65,7 @@ namespace NzbDrone.Core.Test.NotificationTests
[Test]
public void should_add_collection_movie_if_valid_mediainfo()
{
- GiventValidMediaInfo(Quality.Bluray1080p, "3/2/0.1", "DTS", "Interlaced");
+ GiventValidMediaInfo(Quality.Bluray1080p, "5.1", "DTS", "Progressive");
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile);
@@ -73,7 +73,7 @@ namespace NzbDrone.Core.Test.NotificationTests
.Verify(v => v.AddToCollection(It.Is(t =>
t.Movies.First().Audio == "dts" &&
t.Movies.First().AudioChannels == "5.1" &&
- t.Movies.First().Resolution == "hd_1080i" &&
+ t.Movies.First().Resolution == "hd_1080p" &&
t.Movies.First().MediaType == "bluray"),
It.IsAny()), Times.Once());
}
@@ -81,7 +81,7 @@ namespace NzbDrone.Core.Test.NotificationTests
[Test]
public void should_format_audio_channels_to_one_decimal_when_adding_collection_movie()
{
- GiventValidMediaInfo(Quality.Bluray1080p, "2/0/0", "DTS", "Interlaced");
+ GiventValidMediaInfo(Quality.Bluray1080p, "2.0", "DTS", "Progressive");
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile);
@@ -89,7 +89,7 @@ namespace NzbDrone.Core.Test.NotificationTests
.Verify(v => v.AddToCollection(It.Is(t =>
t.Movies.First().Audio == "dts" &&
t.Movies.First().AudioChannels == "2.0" &&
- t.Movies.First().Resolution == "hd_1080i" &&
+ t.Movies.First().Resolution == "hd_1080p" &&
t.Movies.First().MediaType == "bluray"),
It.IsAny()), Times.Once());
}
diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs
index 69f916c9b..03fc31c5d 100644
--- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs
+++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs
@@ -19,6 +19,7 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
{
+ [Platform(Exclude = "Win")]
[TestFixture]
public class FileNameBuilderFixture : CoreTest
@@ -368,35 +369,44 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
_movieFile.MediaInfo = new MediaInfoModel()
{
- VideoFormat = "AVC",
- AudioFormat = "DTS",
- AudioLanguages = "English/Spanish",
- Subtitles = "English/Spanish/Italian"
+ VideoFormat = "h264",
+ AudioFormat = "dts",
+ AudioLanguages = new List { "eng", "spa" },
+ Subtitles = new List { "eng", "spa", "ita" }
};
Subject.BuildFileName(_movie, _movieFile)
.Should().Be("South.Park.H264.DTS[EN+ES].[EN+ES+IT]");
}
- [TestCase("Norwegian Bokmal", "NB")]
- [TestCase("Swedis", "SV")]
- [TestCase("Chinese", "ZH")]
+ [TestCase("nob", "NB")]
+ [TestCase("swe", "SV")]
+ [TestCase("zho", "ZH")]
+ [TestCase("chi", "ZH")]
+ [TestCase("fre", "FR")]
+ [TestCase("rum", "RO")]
+ [TestCase("per", "FA")]
+ [TestCase("ger", "DE")]
+ [TestCase("cze", "CS")]
+ [TestCase("ice", "IS")]
+ [TestCase("dut", "NL")]
+ [TestCase("nor", "NO")]
public void should_format_languagecodes_properly(string language, string code)
{
_namingConfig.StandardMovieFormat = "{Movie.Title}.{MEDIAINFO.FULL}";
_movieFile.MediaInfo = new MediaInfoModel()
{
- VideoCodec = "AVC",
- AudioFormat = "DTS",
- AudioChannelsContainer = 6,
- AudioLanguages = "English",
- Subtitles = language,
+ VideoFormat = "h264",
+ AudioFormat = "dts",
+ AudioChannels = 6,
+ AudioLanguages = new List { "eng" },
+ Subtitles = new List { language },
SchemaRevision = 3
};
Subject.BuildFileName(_movie, _movieFile)
- .Should().Be($"South.Park.X264.DTS.[{code}]");
+ .Should().Be($"South.Park.H264.DTS.[{code}]");
}
[Test]
@@ -406,16 +416,17 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
_movieFile.MediaInfo = new MediaInfoModel()
{
- VideoFormat = "AVC",
- AudioFormat = "DTS",
- AudioLanguages = "English",
- Subtitles = "English/Spanish/Italian"
+ VideoFormat = "h264",
+ AudioFormat = "dts",
+ AudioLanguages = new List { "eng" },
+ Subtitles = new List { "eng", "spa", "ita" }
};
Subject.BuildFileName(_movie, _movieFile)
.Should().Be("South.Park.H264.DTS.[EN+ES+IT]");
}
+ [Ignore("not currently supported")]
[Test]
public void should_format_mediainfo_3d_properly()
{
@@ -423,11 +434,11 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
_movieFile.MediaInfo = new MediaInfoModel()
{
- VideoFormat = "AVC",
+ VideoFormat = "h264",
VideoMultiViewCount = 2,
- AudioFormat = "DTS",
- AudioLanguages = "English",
- Subtitles = "English/Spanish/Italian"
+ AudioFormat = "dts",
+ AudioLanguages = new List { "eng" },
+ Subtitles = new List { "eng", "spa", "ita" }
};
Subject.BuildFileName(_movie, _movieFile)
@@ -631,8 +642,8 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
.Should().Be(releaseGroup);
}
- [TestCase("English", "")]
- [TestCase("English/German", "[EN+DE]")]
+ [TestCase("eng", "")]
+ [TestCase("eng/deu", "[EN+DE]")]
public void should_format_audio_languages(string audioLanguages, string expected)
{
_movieFile.ReleaseGroup = null;
@@ -645,8 +656,8 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
.Should().Be(expected);
}
- [TestCase("English", "[EN]")]
- [TestCase("English/German", "[EN+DE]")]
+ [TestCase("eng", "[EN]")]
+ [TestCase("eng/deu", "[EN+DE]")]
public void should_format_audio_languages_all(string audioLanguages, string expected)
{
_movieFile.ReleaseGroup = null;
@@ -659,19 +670,15 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
.Should().Be(expected);
}
- [TestCase(8, "BT.601 NTSC", "BT.709", "South.Park")]
- [TestCase(10, "BT.2020", "PQ", "South.Park.HDR")]
- [TestCase(10, "BT.2020", "HLG", "South.Park.HDR")]
- [TestCase(0, null, null, "South.Park")]
- public void should_include_hdr_for_mediainfo_videodynamicrange_with_valid_properties(int bitDepth,
- string colourPrimaries,
- string transferCharacteristics,
- string expectedName)
+ [TestCase(HdrFormat.None, "South.Park")]
+ [TestCase(HdrFormat.Hlg10, "South.Park.HDR")]
+ [TestCase(HdrFormat.Hdr10, "South.Park.HDR")]
+ public void should_include_hdr_for_mediainfo_videodynamicrange_with_valid_properties(HdrFormat hdrFormat, string expectedName)
{
_namingConfig.StandardMovieFormat =
"{Movie.Title}.{MediaInfo VideoDynamicRange}";
- GivenMediaInfoModel(videoBitDepth: bitDepth, videoColourPrimaries: colourPrimaries, videoTransferCharacteristics: transferCharacteristics);
+ GivenMediaInfoModel(hdrFormat: hdrFormat);
Subject.BuildFileName(_movie, _movieFile)
.Should().Be(expectedName);
@@ -743,26 +750,24 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
Mocker.GetMock().Verify(v => v.Update(_movieFile, _movie), Times.Never());
}
- private void GivenMediaInfoModel(string videoCodec = "AVC",
- string audioCodec = "DTS",
+ private void GivenMediaInfoModel(string videoCodec = "h264",
+ string audioCodec = "dts",
int audioChannels = 6,
int videoBitDepth = 8,
- string videoColourPrimaries = "",
- string videoTransferCharacteristics = "",
- string audioLanguages = "English",
- string subtitles = "English/Spanish/Italian",
+ HdrFormat hdrFormat = HdrFormat.None,
+ string audioLanguages = "eng",
+ string subtitles = "eng/spa/ita",
int schemaRevision = 5)
{
_movieFile.MediaInfo = new MediaInfoModel
{
- VideoCodec = videoCodec,
+ VideoFormat = videoCodec,
AudioFormat = audioCodec,
- AudioChannelsContainer = audioChannels,
- AudioLanguages = audioLanguages,
- Subtitles = subtitles,
+ AudioChannels = audioChannels,
+ AudioLanguages = audioLanguages.Split("/").ToList(),
+ Subtitles = subtitles.Split("/").ToList(),
VideoBitDepth = videoBitDepth,
- VideoColourPrimaries = videoColourPrimaries,
- VideoTransferCharacteristics = videoTransferCharacteristics,
+ VideoHdrFormat = hdrFormat,
SchemaRevision = schemaRevision
};
}
diff --git a/src/NzbDrone.Core/Datastore/Migration/199_mediainfo_to_ffmpeg.cs b/src/NzbDrone.Core/Datastore/Migration/199_mediainfo_to_ffmpeg.cs
new file mode 100644
index 000000000..48a108c4d
--- /dev/null
+++ b/src/NzbDrone.Core/Datastore/Migration/199_mediainfo_to_ffmpeg.cs
@@ -0,0 +1,903 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Globalization;
+using System.Linq;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
+using Dapper;
+using FluentMigrator;
+using NzbDrone.Common.EnvironmentInfo;
+using NzbDrone.Common.Extensions;
+using NzbDrone.Common.Serializer;
+using NzbDrone.Core.Datastore.Migration.Framework;
+using NzbDrone.Core.MediaFiles.MediaInfo;
+
+namespace NzbDrone.Core.Datastore.Migration
+{
+ [Migration(199)]
+ public class mediainfo_to_ffmpeg : NzbDroneMigrationBase
+ {
+ private readonly JsonSerializerOptions _serializerSettings;
+
+ public mediainfo_to_ffmpeg()
+ {
+ var serializerSettings = new JsonSerializerOptions
+ {
+ AllowTrailingCommas = true,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ PropertyNameCaseInsensitive = true,
+ DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ WriteIndented = true
+ };
+
+ serializerSettings.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, true));
+ serializerSettings.Converters.Add(new STJTimeSpanConverter());
+ serializerSettings.Converters.Add(new STJUtcConverter());
+
+ _serializerSettings = serializerSettings;
+ }
+
+ protected override void MainDbUpgrade()
+ {
+ Execute.WithConnection(MigrateToFfprobe);
+ }
+
+ private void MigrateToFfprobe(IDbConnection conn, IDbTransaction tran)
+ {
+ var existing = conn.Query("SELECT Id, MediaInfo, SceneName FROM MovieFiles");
+
+ var updated = new List();
+
+ foreach (var row in existing)
+ {
+ if (row.MediaInfo.IsNullOrWhiteSpace())
+ {
+ continue;
+ }
+
+ // basic parse to check schema revision
+ // in case user already tested ffmpeg branch
+ var mediaInfoVersion = JsonSerializer.Deserialize(row.MediaInfo, _serializerSettings);
+ if (mediaInfoVersion.SchemaRevision >= 8)
+ {
+ continue;
+ }
+
+ // parse and migrate
+ var mediaInfo = JsonSerializer.Deserialize(row.MediaInfo, _serializerSettings);
+
+ var ffprobe = MigrateMediaInfo(mediaInfo, row.SceneName);
+
+ updated.Add(new MediaInfoRaw
+ {
+ Id = row.Id,
+ MediaInfo = JsonSerializer.Serialize(ffprobe, _serializerSettings)
+ });
+ }
+
+ var updateSql = "UPDATE MovieFiles SET MediaInfo = @MediaInfo WHERE Id = @Id";
+ conn.Execute(updateSql, updated, transaction: tran);
+ }
+
+ public MediaInfo199 MigrateMediaInfo(MediaInfo198 old, string sceneName)
+ {
+ var m = new MediaInfo199
+ {
+ SchemaRevision = old.SchemaRevision,
+ ContainerFormat = old.ContainerFormat,
+ VideoProfile = old.VideoProfile,
+ VideoBitrate = old.VideoBitrate,
+ VideoBitDepth = old.VideoBitDepth,
+ VideoMultiViewCount = old.VideoMultiViewCount,
+ VideoColourPrimaries = MigratePrimaries(old.VideoColourPrimaries),
+ VideoTransferCharacteristics = MigrateTransferCharacteristics(old.VideoTransferCharacteristics),
+ Height = old.Height,
+ Width = old.Width,
+ AudioBitrate = old.AudioBitrate,
+ RunTime = old.RunTime,
+ AudioStreamCount = old.AudioStreamCount,
+ VideoFps = old.VideoFps,
+ ScanType = old.ScanType,
+ AudioLanguages = MigrateLanguages(old.AudioLanguages),
+ Subtitles = MigrateLanguages(old.Subtitles)
+ };
+
+ m.VideoHdrFormat = MigrateHdrFormat(old);
+
+ MigrateVideoCodec(old, m, sceneName);
+ MigrateAudioCodec(old, m);
+ MigrateAudioChannelPositions(old, m);
+
+ m.AudioChannels = old.AudioChannelsStream > 0 ? old.AudioChannelsStream : old.AudioChannelsContainer;
+
+ return m;
+ }
+
+ private void MigrateVideoCodec(MediaInfo198 mediaInfo, MediaInfo199 m, string sceneName)
+ {
+ if (mediaInfo.VideoFormat == null)
+ {
+ MigrateVideoCodecLegacy(mediaInfo, m, sceneName);
+ return;
+ }
+
+ var videoFormat = mediaInfo.VideoFormat.Trim().Split(new[] { " / " }, StringSplitOptions.RemoveEmptyEntries);
+ var videoCodecID = mediaInfo.VideoCodecID ?? string.Empty;
+ var videoCodecLibrary = mediaInfo.VideoCodecLibrary ?? string.Empty;
+
+ var result = mediaInfo.VideoFormat.Trim();
+
+ m.VideoFormat = result;
+ m.VideoCodecID = null;
+
+ if (videoFormat.ContainsIgnoreCase("x264"))
+ {
+ m.VideoFormat = "h264";
+ m.VideoCodecID = "x264";
+ return;
+ }
+
+ if (videoFormat.ContainsIgnoreCase("AVC") || videoFormat.ContainsIgnoreCase("V.MPEG4/ISO/AVC"))
+ {
+ m.VideoFormat = "h264";
+
+ if (videoCodecLibrary.StartsWithIgnoreCase("x264"))
+ {
+ m.VideoCodecID = "x264";
+ }
+
+ return;
+ }
+
+ if (videoFormat.ContainsIgnoreCase("HEVC") || videoFormat.ContainsIgnoreCase("V_MPEGH/ISO/HEVC"))
+ {
+ m.VideoFormat = "hevc";
+ if (videoCodecLibrary.StartsWithIgnoreCase("x265"))
+ {
+ m.VideoCodecID = "x265";
+ }
+
+ return;
+ }
+
+ if (videoFormat.ContainsIgnoreCase("MPEG Video"))
+ {
+ if (videoCodecID == "2" || videoCodecID == "V_MPEG2")
+ {
+ m.VideoFormat = "mpeg2video";
+ }
+
+ if (videoCodecID.IsNullOrWhiteSpace())
+ {
+ m.VideoFormat = "MPEG";
+ }
+ }
+
+ if (videoFormat.ContainsIgnoreCase("MPEG-2 Video"))
+ {
+ m.VideoFormat = "mpeg2video";
+ }
+
+ if (videoFormat.ContainsIgnoreCase("MPEG-4 Visual"))
+ {
+ m.VideoFormat = "mpeg4";
+
+ if (videoCodecID.ContainsIgnoreCase("XVID") ||
+ videoCodecLibrary.StartsWithIgnoreCase("XviD"))
+ {
+ m.VideoCodecID = "XVID";
+ }
+
+ if (videoCodecID.ContainsIgnoreCase("DIV3") ||
+ videoCodecID.ContainsIgnoreCase("DIVX") ||
+ videoCodecID.ContainsIgnoreCase("DX50") ||
+ videoCodecLibrary.StartsWithIgnoreCase("DivX"))
+ {
+ m.VideoCodecID = "DIVX";
+ }
+
+ return;
+ }
+
+ if (videoFormat.ContainsIgnoreCase("MPEG-4 Visual") || videoFormat.ContainsIgnoreCase("mp4v"))
+ {
+ m.VideoFormat = "mpeg4";
+
+ result = GetSceneNameMatch(sceneName, "XviD", "DivX", "");
+
+ if (result == "XviD")
+ {
+ m.VideoCodecID = "XVID";
+ }
+
+ if (result == "DivX")
+ {
+ m.VideoCodecID = "DIVX";
+ }
+
+ return;
+ }
+
+ if (videoFormat.ContainsIgnoreCase("VC-1"))
+ {
+ m.VideoFormat = "vc1";
+ return;
+ }
+
+ if (videoFormat.ContainsIgnoreCase("AV1"))
+ {
+ m.VideoFormat = "av1";
+ return;
+ }
+
+ if (videoFormat.ContainsIgnoreCase("VP6") || videoFormat.ContainsIgnoreCase("VP7") ||
+ videoFormat.ContainsIgnoreCase("VP8") || videoFormat.ContainsIgnoreCase("VP9"))
+ {
+ m.VideoFormat = videoFormat.First().ToLowerInvariant();
+ return;
+ }
+
+ if (videoFormat.ContainsIgnoreCase("WMV1") || videoFormat.ContainsIgnoreCase("WMV2"))
+ {
+ m.VideoFormat = "WMV";
+ return;
+ }
+
+ if (videoFormat.ContainsIgnoreCase("DivX") || videoFormat.ContainsIgnoreCase("div3"))
+ {
+ m.VideoFormat = "mpeg4";
+ m.VideoCodecID = "DIVX";
+ return;
+ }
+
+ if (videoFormat.ContainsIgnoreCase("XviD"))
+ {
+ m.VideoFormat = "mpeg4";
+ m.VideoCodecID = "XVID";
+ return;
+ }
+
+ if (videoFormat.ContainsIgnoreCase("V_QUICKTIME") ||
+ videoFormat.ContainsIgnoreCase("RealVideo 4"))
+ {
+ m.VideoFormat = "qtrle";
+ return;
+ }
+
+ if (videoFormat.ContainsIgnoreCase("mp42") ||
+ videoFormat.ContainsIgnoreCase("mp43"))
+ {
+ m.VideoFormat = "mpeg4";
+ return;
+ }
+ }
+
+ private void MigrateVideoCodecLegacy(MediaInfo198 mediaInfo, MediaInfo199 m, string sceneName)
+ {
+ var videoCodec = mediaInfo.VideoCodec;
+
+ m.VideoFormat = videoCodec;
+ m.VideoCodecID = null;
+
+ if (videoCodec.IsNullOrWhiteSpace())
+ {
+ m.VideoFormat = null;
+ return;
+ }
+
+ if (videoCodec == "AVC")
+ {
+ m.VideoFormat = "h264";
+ }
+
+ if (videoCodec == "V_MPEGH/ISO/HEVC" || videoCodec == "HEVC")
+ {
+ m.VideoFormat = "hevc";
+ }
+
+ if (videoCodec == "MPEG-2 Video")
+ {
+ m.VideoFormat = "mpeg2video";
+ }
+
+ if (videoCodec == "MPEG-4 Visual")
+ {
+ var result = GetSceneNameMatch(sceneName, "DivX", "XviD");
+ if (result == "DivX")
+ {
+ m.VideoFormat = "mpeg4";
+ m.VideoCodecID = "DIVX";
+ }
+
+ m.VideoFormat = "mpeg4";
+ m.VideoCodecID = "XVID";
+ }
+
+ if (videoCodec.StartsWithIgnoreCase("XviD"))
+ {
+ m.VideoFormat = "mpeg4";
+ m.VideoCodecID = "XVID";
+ }
+
+ if (videoCodec.StartsWithIgnoreCase("DivX"))
+ {
+ m.VideoFormat = "mpeg4";
+ m.VideoCodecID = "DIVX";
+ }
+
+ if (videoCodec.EqualsIgnoreCase("VC-1"))
+ {
+ m.VideoFormat = "vc1";
+ }
+ }
+
+ private HdrFormat MigrateHdrFormat(MediaInfo198 mediaInfo)
+ {
+ if (mediaInfo.VideoHdrFormatCompatibility.IsNotNullOrWhiteSpace())
+ {
+ if (mediaInfo.VideoHdrFormatCompatibility.ContainsIgnoreCase("HLG"))
+ {
+ return HdrFormat.Hlg10;
+ }
+
+ if (mediaInfo.VideoHdrFormatCompatibility.ContainsIgnoreCase("dolby"))
+ {
+ return HdrFormat.DolbyVision;
+ }
+
+ if (mediaInfo.VideoHdrFormatCompatibility.ContainsIgnoreCase("dolby"))
+ {
+ return HdrFormat.DolbyVision;
+ }
+
+ if (mediaInfo.VideoHdrFormatCompatibility.ContainsIgnoreCase("hdr10+"))
+ {
+ return HdrFormat.Hdr10Plus;
+ }
+
+ if (mediaInfo.VideoHdrFormatCompatibility.ContainsIgnoreCase("hdr10"))
+ {
+ return HdrFormat.Hdr10;
+ }
+ }
+
+ return VideoFileInfoReader.GetHdrFormat(mediaInfo.VideoBitDepth, mediaInfo.VideoColourPrimaries, mediaInfo.VideoTransferCharacteristics, new ());
+ }
+
+ private void MigrateAudioCodec(MediaInfo198 mediaInfo, MediaInfo199 m)
+ {
+ if (mediaInfo.AudioCodecID == null)
+ {
+ MigrateAudioCodecLegacy(mediaInfo, m);
+ return;
+ }
+
+ var audioFormat = mediaInfo.AudioFormat.Trim().Split(new[] { " / " }, StringSplitOptions.RemoveEmptyEntries);
+ var audioCodecID = mediaInfo.AudioCodecID ?? string.Empty;
+ var splitAdditionalFeatures = (mediaInfo.AudioAdditionalFeatures ?? string.Empty).Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
+
+ m.AudioFormat = "";
+
+ if (audioFormat.Empty())
+ {
+ return;
+ }
+
+ if (audioFormat.ContainsIgnoreCase("Atmos"))
+ {
+ m.AudioFormat = "truehd";
+ m.AudioCodecID = "thd+";
+ return;
+ }
+
+ if (audioFormat.ContainsIgnoreCase("MLP FBA"))
+ {
+ m.AudioFormat = "truehd";
+
+ if (splitAdditionalFeatures.ContainsIgnoreCase("16-ch"))
+ {
+ m.AudioCodecID = "thd+";
+ return;
+ }
+
+ return;
+ }
+
+ if (audioFormat.ContainsIgnoreCase("TrueHD"))
+ {
+ m.AudioFormat = "truehd";
+ return;
+ }
+
+ if (audioFormat.ContainsIgnoreCase("FLAC"))
+ {
+ m.AudioFormat = "flac";
+ return;
+ }
+
+ if (audioFormat.ContainsIgnoreCase("DTS"))
+ {
+ m.AudioFormat = "dts";
+
+ if (splitAdditionalFeatures.ContainsIgnoreCase("XLL"))
+ {
+ if (splitAdditionalFeatures.ContainsIgnoreCase("X"))
+ {
+ m.AudioProfile = "DTS:X";
+ return;
+ }
+
+ m.AudioProfile = "DTS-HD MA";
+ return;
+ }
+
+ if (splitAdditionalFeatures.ContainsIgnoreCase("ES"))
+ {
+ m.AudioProfile = "DTS-ES";
+ return;
+ }
+
+ if (splitAdditionalFeatures.ContainsIgnoreCase("XBR"))
+ {
+ m.AudioProfile = "DTS-HD HRA";
+ return;
+ }
+
+ return;
+ }
+
+ if (audioFormat.ContainsIgnoreCase("E-AC-3"))
+ {
+ m.AudioFormat = "eac3";
+
+ if (splitAdditionalFeatures.ContainsIgnoreCase("JOC"))
+ {
+ m.AudioCodecID = "ec+3";
+ }
+
+ return;
+ }
+
+ if (audioFormat.ContainsIgnoreCase("AC-3"))
+ {
+ m.AudioFormat = "ac3";
+ return;
+ }
+
+ if (audioFormat.ContainsIgnoreCase("AAC"))
+ {
+ m.AudioFormat = "aac";
+
+ if (audioCodecID == "A_AAC/MPEG4/LC/SBR")
+ {
+ m.AudioCodecID = audioCodecID;
+ }
+
+ return;
+ }
+
+ if (audioFormat.ContainsIgnoreCase("mp3"))
+ {
+ m.AudioFormat = "mp3";
+ return;
+ }
+
+ if (audioFormat.ContainsIgnoreCase("MPEG Audio"))
+ {
+ if (mediaInfo.AudioCodecID == "55" || mediaInfo.AudioCodecID == "A_MPEG/L3" || mediaInfo.AudioProfile == "Layer 3")
+ {
+ m.AudioFormat = "mp3";
+ return;
+ }
+
+ if (mediaInfo.AudioCodecID == "A_MPEG/L2" || mediaInfo.AudioProfile == "Layer 2")
+ {
+ m.AudioFormat = "mp2";
+ }
+ }
+
+ if (audioFormat.ContainsIgnoreCase("Opus"))
+ {
+ m.AudioFormat = "opus";
+ return;
+ }
+
+ if (audioFormat.ContainsIgnoreCase("PCM"))
+ {
+ m.AudioFormat = "pcm_s16le";
+ return;
+ }
+
+ if (audioFormat.ContainsIgnoreCase("ADPCM"))
+ {
+ m.AudioFormat = "pcm_s16le";
+ return;
+ }
+
+ if (audioFormat.ContainsIgnoreCase("Vorbis"))
+ {
+ m.AudioFormat = "vorbis";
+ return;
+ }
+
+ if (audioFormat.ContainsIgnoreCase("WMA"))
+ {
+ m.AudioFormat = "wmav1";
+ return;
+ }
+ }
+
+ private void MigrateAudioCodecLegacy(MediaInfo198 mediaInfo, MediaInfo199 m)
+ {
+ var audioFormat = mediaInfo.AudioFormat;
+
+ m.AudioFormat = audioFormat;
+
+ if (audioFormat.IsNullOrWhiteSpace())
+ {
+ m.AudioFormat = null;
+ return;
+ }
+
+ if (audioFormat.EqualsIgnoreCase("AC-3"))
+ {
+ m.AudioFormat = "ac3";
+ return;
+ }
+
+ if (audioFormat.EqualsIgnoreCase("E-AC-3"))
+ {
+ m.AudioFormat = "eac3";
+ return;
+ }
+
+ if (audioFormat.EqualsIgnoreCase("AAC"))
+ {
+ m.AudioFormat = "aac";
+ return;
+ }
+
+ if (audioFormat.EqualsIgnoreCase("MPEG Audio") && mediaInfo.AudioProfile == "Layer 3")
+ {
+ m.AudioFormat = "mp3";
+ return;
+ }
+
+ if (audioFormat.EqualsIgnoreCase("DTS"))
+ {
+ m.AudioFormat = "DTS";
+ return;
+ }
+
+ if (audioFormat.EqualsIgnoreCase("TrueHD"))
+ {
+ m.AudioFormat = "truehd";
+ return;
+ }
+
+ if (audioFormat.EqualsIgnoreCase("FLAC"))
+ {
+ m.AudioFormat = "flac";
+ return;
+ }
+
+ if (audioFormat.EqualsIgnoreCase("Vorbis"))
+ {
+ m.AudioFormat = "vorbis";
+ return;
+ }
+
+ if (audioFormat.EqualsIgnoreCase("Opus"))
+ {
+ m.AudioFormat = "opus";
+ return;
+ }
+ }
+
+ private void MigrateAudioChannelPositions(MediaInfo198 mediaInfo, MediaInfo199 m)
+ {
+ var audioChannels = FormatAudioChannelsFromAudioChannelPositions(mediaInfo);
+
+ if (audioChannels == null || audioChannels == 0.0m)
+ {
+ audioChannels = FormatAudioChannelsFromAudioChannelPositionsText(mediaInfo);
+ }
+
+ if (audioChannels == null || audioChannels == 0.0m)
+ {
+ audioChannels = FormatAudioChannelsFromAudioChannels(mediaInfo);
+ }
+
+ audioChannels ??= 0;
+
+ m.AudioChannelPositions = audioChannels.ToString();
+ }
+
+ private decimal? FormatAudioChannelsFromAudioChannelPositions(MediaInfo198 mediaInfo)
+ {
+ var audioChannelPositions = mediaInfo.AudioChannelPositions;
+
+ if (audioChannelPositions.IsNullOrWhiteSpace())
+ {
+ return null;
+ }
+
+ try
+ {
+ if (audioChannelPositions.Contains("+"))
+ {
+ return audioChannelPositions.Split('+')
+ .Sum(s => decimal.Parse(s.Trim(), CultureInfo.InvariantCulture));
+ }
+
+ if (audioChannelPositions.Contains("/"))
+ {
+ var channelStringList = Regex.Replace(audioChannelPositions,
+ @"^\d+\sobjects",
+ "",
+ RegexOptions.Compiled | RegexOptions.IgnoreCase)
+ .Replace("Object Based / ", "")
+ .Split(new string[] { " / " }, StringSplitOptions.RemoveEmptyEntries)
+ .FirstOrDefault()
+ ?.Split('/');
+
+ var positions = default(decimal);
+
+ if (channelStringList == null)
+ {
+ return 0;
+ }
+
+ foreach (var channel in channelStringList)
+ {
+ var channelSplit = channel.Split(new string[] { "." }, StringSplitOptions.None);
+
+ if (channelSplit.Length == 3)
+ {
+ positions += decimal.Parse(string.Format("{0}.{1}", channelSplit[1], channelSplit[2]), CultureInfo.InvariantCulture);
+ }
+ else
+ {
+ positions += decimal.Parse(channel, CultureInfo.InvariantCulture);
+ }
+ }
+
+ return positions;
+ }
+ }
+ catch
+ {
+ }
+
+ return null;
+ }
+
+ private decimal? FormatAudioChannelsFromAudioChannelPositionsText(MediaInfo198 mediaInfo)
+ {
+ var audioChannelPositionsTextContainer = mediaInfo.AudioChannelPositionsTextContainer;
+ var audioChannelPositionsTextStream = mediaInfo.AudioChannelPositionsTextStream;
+ var audioChannelsContainer = mediaInfo.AudioChannelsContainer;
+ var audioChannelsStream = mediaInfo.AudioChannelsStream;
+
+ //Skip if the positions texts give us nothing
+ if ((audioChannelPositionsTextContainer.IsNullOrWhiteSpace() || audioChannelPositionsTextContainer == "Object Based") &&
+ (audioChannelPositionsTextStream.IsNullOrWhiteSpace() || audioChannelPositionsTextStream == "Object Based"))
+ {
+ return null;
+ }
+
+ try
+ {
+ if (audioChannelsStream > 0)
+ {
+ return audioChannelPositionsTextStream.ContainsIgnoreCase("LFE") ? audioChannelsStream - 1 + 0.1m : audioChannelsStream;
+ }
+
+ return audioChannelPositionsTextContainer.ContainsIgnoreCase("LFE") ? audioChannelsContainer - 1 + 0.1m : audioChannelsContainer;
+ }
+ catch
+ {
+ }
+
+ return null;
+ }
+
+ private decimal? FormatAudioChannelsFromAudioChannels(MediaInfo198 mediaInfo)
+ {
+ var audioChannelsContainer = mediaInfo.AudioChannelsContainer;
+ var audioChannelsStream = mediaInfo.AudioChannelsStream;
+
+ var audioFormat = (mediaInfo.AudioFormat ?? string.Empty).Trim().Split(new[] { " / " }, StringSplitOptions.RemoveEmptyEntries);
+ var splitAdditionalFeatures = (mediaInfo.AudioAdditionalFeatures ?? string.Empty).Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
+
+ // Workaround https://github.com/MediaArea/MediaInfo/issues/299 for DTS-X Audio
+ if (audioFormat.ContainsIgnoreCase("DTS") &&
+ splitAdditionalFeatures.ContainsIgnoreCase("XLL") &&
+ splitAdditionalFeatures.ContainsIgnoreCase("X") &&
+ audioChannelsContainer > 0)
+ {
+ return audioChannelsContainer - 1 + 0.1m;
+ }
+
+ // FLAC 6 channels is likely 5.1
+ if (audioFormat.ContainsIgnoreCase("FLAC") && audioChannelsContainer == 6)
+ {
+ return 5.1m;
+ }
+
+ if (mediaInfo.SchemaRevision > 5)
+ {
+ return audioChannelsStream > 0 ? audioChannelsStream : audioChannelsContainer;
+ }
+
+ if (mediaInfo.SchemaRevision >= 3)
+ {
+ return audioChannelsContainer;
+ }
+
+ return null;
+ }
+
+ private List MigrateLanguages(string mediaInfoLanguages)
+ {
+ var languages = new List();
+
+ var tokens = mediaInfoLanguages.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList();
+
+ var cultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures);
+ for (int i = 0; i < tokens.Count; i++)
+ {
+ if (tokens[i] == "Swedis")
+ {
+ // Probably typo in mediainfo (should be 'Swedish')
+ languages.Add("swe");
+ continue;
+ }
+
+ if (tokens[i] == "Chinese" && OsInfo.IsNotWindows)
+ {
+ // Mono only has 'Chinese (Simplified)' & 'Chinese (Traditional)'
+ languages.Add("zho");
+ continue;
+ }
+
+ if (tokens[i] == "Norwegian")
+ {
+ languages.Add("nor");
+ continue;
+ }
+
+ try
+ {
+ var cultureInfo = cultures.FirstOrDefault(p => p.EnglishName.RemoveAccent() == tokens[i]);
+
+ if (cultureInfo != null)
+ {
+ languages.Add(cultureInfo.ThreeLetterISOLanguageName.ToLowerInvariant());
+ }
+ }
+ catch
+ {
+ }
+ }
+
+ return languages;
+ }
+
+ private string MigratePrimaries(string primary)
+ {
+ return primary.Replace("BT.", "bt");
+ }
+
+ private string MigrateTransferCharacteristics(string transferCharacteristics)
+ {
+ if (transferCharacteristics == "PQ")
+ {
+ return "smpte2084";
+ }
+
+ if (transferCharacteristics == "HLG")
+ {
+ return "arib-std-b67";
+ }
+
+ return "bt709";
+ }
+
+ private static string GetSceneNameMatch(string sceneName, params string[] tokens)
+ {
+ sceneName = sceneName.IsNotNullOrWhiteSpace() ? Parser.Parser.RemoveFileExtension(sceneName) : string.Empty;
+
+ foreach (var token in tokens)
+ {
+ if (sceneName.ContainsIgnoreCase(token))
+ {
+ return token;
+ }
+ }
+
+ // Last token is the default.
+ return tokens.Last();
+ }
+
+ public class MediaInfoRaw : ModelBase
+ {
+ public string MediaInfo { get; set; }
+ public string SceneName { get; set; }
+ }
+
+ public class MediaInfoBase
+ {
+ public int SchemaRevision { get; set; }
+ }
+
+ public class MediaInfo198 : MediaInfoBase
+ {
+ public string ContainerFormat { get; set; }
+
+ // Deprecated according to MediaInfo
+ public string VideoCodec { get; set; }
+ public string VideoFormat { get; set; }
+ public string VideoCodecID { get; set; }
+ public string VideoProfile { get; set; }
+ public string VideoCodecLibrary { get; set; }
+ public int VideoBitrate { get; set; }
+ public int VideoBitDepth { get; set; }
+ public int VideoMultiViewCount { get; set; }
+ public string VideoColourPrimaries { get; set; }
+ public string VideoTransferCharacteristics { get; set; }
+ public string VideoHdrFormat { get; set; }
+ public string VideoHdrFormatCompatibility { get; set; }
+ public int Width { get; set; }
+ public int Height { get; set; }
+ public string AudioFormat { get; set; }
+ public string AudioCodecID { get; set; }
+ public string AudioCodecLibrary { get; set; }
+ public string AudioAdditionalFeatures { get; set; }
+ public int AudioBitrate { get; set; }
+ public TimeSpan RunTime { get; set; }
+ public int AudioStreamCount { get; set; }
+ public int AudioChannelsContainer { get; set; }
+ public int AudioChannelsStream { get; set; }
+ public string AudioChannelPositions { get; set; }
+ public string AudioChannelPositionsTextContainer { get; set; }
+ public string AudioChannelPositionsTextStream { 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; }
+ }
+
+ public class MediaInfo199 : MediaInfoBase
+ {
+ public string ContainerFormat { get; set; }
+ public string VideoFormat { get; set; }
+ public string VideoCodecID { get; set; }
+ public string VideoProfile { get; set; }
+ public int VideoBitrate { get; set; }
+ public int VideoBitDepth { get; set; }
+ public int VideoMultiViewCount { get; set; }
+ public string VideoColourPrimaries { get; set; }
+ public string VideoTransferCharacteristics { get; set; }
+ public HdrFormat VideoHdrFormat { get; set; }
+ public int Height { get; set; }
+ public int Width { get; set; }
+ public string AudioFormat { get; set; }
+ public string AudioCodecID { get; set; }
+ public string AudioProfile { get; set; }
+ public int AudioBitrate { get; set; }
+ public TimeSpan RunTime { get; set; }
+ public int AudioStreamCount { get; set; }
+ public int AudioChannels { get; set; }
+ public string AudioChannelPositions { get; set; }
+ public decimal VideoFps { get; set; }
+ public List AudioLanguages { get; set; }
+ public List Subtitles { get; set; }
+ public string ScanType { get; set; }
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs b/src/NzbDrone.Core/Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs
index 3741b74b1..9f9d6e98d 100644
--- a/src/NzbDrone.Core/Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs
+++ b/src/NzbDrone.Core/Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs
@@ -307,14 +307,14 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
streamDetails.Add(video);
var audio = new XElement("audio");
- var audioChannelCount = movieFile.MediaInfo.AudioChannelsStream > 0 ? movieFile.MediaInfo.AudioChannelsStream : movieFile.MediaInfo.AudioChannelsContainer;
+ var audioChannelCount = movieFile.MediaInfo.AudioChannels;
audio.Add(new XElement("bitrate", movieFile.MediaInfo.AudioBitrate));
audio.Add(new XElement("channels", audioChannelCount));
audio.Add(new XElement("codec", MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, sceneName)));
audio.Add(new XElement("language", movieFile.MediaInfo.AudioLanguages));
streamDetails.Add(audio);
- if (movieFile.MediaInfo.Subtitles != null && movieFile.MediaInfo.Subtitles.Length > 0)
+ if (movieFile.MediaInfo.Subtitles != null && movieFile.MediaInfo.Subtitles.Count > 0)
{
var subtitle = new XElement("subtitle");
subtitle.Add(new XElement("language", movieFile.MediaInfo.Subtitles));
diff --git a/src/NzbDrone.Core/HealthCheck/Checks/MediaInfoDllCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/MediaInfoDllCheck.cs
deleted file mode 100644
index da56a8431..000000000
--- a/src/NzbDrone.Core/HealthCheck/Checks/MediaInfoDllCheck.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System;
-using System.Runtime.CompilerServices;
-using NzbDrone.Core.Localization;
-using NzbDrone.Core.MediaFiles.MediaInfo;
-
-namespace NzbDrone.Core.HealthCheck.Checks
-{
- public class MediaInfoDllCheck : HealthCheckBase
- {
- public MediaInfoDllCheck(ILocalizationService localizationService)
- : base(localizationService)
- {
- }
-
- [MethodImpl(MethodImplOptions.NoOptimization)]
- public override HealthCheck Check()
- {
- try
- {
- var mediaInfo = new MediaInfo();
- }
- catch (Exception e)
- {
- return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("MediaInfoDllCheckMessage"), e.Message), "#mediainfo-not-loaded");
- }
-
- return new HealthCheck(GetType());
- }
- }
-}
diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/HdrFormat.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/HdrFormat.cs
new file mode 100644
index 000000000..de014cd6d
--- /dev/null
+++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/HdrFormat.cs
@@ -0,0 +1,12 @@
+namespace NzbDrone.Core.MediaFiles.MediaInfo
+{
+ public enum HdrFormat
+ {
+ None,
+ Pq10,
+ Hdr10,
+ Hdr10Plus,
+ Hlg10,
+ DolbyVision
+ }
+}
diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs
index 168d11853..537217f27 100644
--- a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs
+++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs
@@ -1,4 +1,3 @@
-using System;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
@@ -12,9 +11,9 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
{
public static class MediaInfoFormatter
{
- private const string ValidHdrColourPrimaries = "BT.2020";
private const string VideoDynamicRangeHdr = "HDR";
- private static readonly string[] ValidHdrTransferFunctions = { "PQ", "HLG" };
+
+ private static readonly Regex PositionRegex = new Regex(@"(?^\d\.\d)", RegexOptions.Compiled);
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(MediaInfoFormatter));
@@ -24,101 +23,95 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
if (audioChannels == null || audioChannels == 0.0m)
{
- audioChannels = FormatAudioChannelsFromAudioChannelPositionsText(mediaInfo);
+ audioChannels = mediaInfo.AudioChannels;
}
- if (audioChannels == null || audioChannels == 0.0m)
- {
- audioChannels = FormatAudioChannelsFromAudioChannels(mediaInfo);
- }
-
- return audioChannels ?? 0;
+ return audioChannels.Value;
}
public static string FormatAudioCodec(MediaInfoModel mediaInfo, string sceneName)
{
- if (mediaInfo.AudioCodecID == null)
+ if (mediaInfo.AudioFormat == null)
{
- return FormatAudioCodecLegacy(mediaInfo, sceneName);
+ return null;
}
- var audioFormat = mediaInfo.AudioFormat.Trim().Split(new[] { " / " }, StringSplitOptions.RemoveEmptyEntries);
+ var audioFormat = mediaInfo.AudioFormat;
var audioCodecID = mediaInfo.AudioCodecID ?? string.Empty;
var audioProfile = mediaInfo.AudioProfile ?? string.Empty;
- var audioCodecLibrary = mediaInfo.AudioCodecLibrary ?? string.Empty;
- var splitAdditionalFeatures = (mediaInfo.AudioAdditionalFeatures ?? string.Empty).Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (audioFormat.Empty())
{
return string.Empty;
}
- if (audioFormat.ContainsIgnoreCase("Atmos"))
+ // see definitions here https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/codec_desc.c
+ if (audioCodecID == "thd+")
{
return "TrueHD Atmos";
}
- if (audioFormat.ContainsIgnoreCase("MLP FBA"))
- {
- if (splitAdditionalFeatures.ContainsIgnoreCase("16-ch"))
- {
- return "TrueHD Atmos";
- }
-
- return "TrueHD";
- }
-
- if (audioFormat.ContainsIgnoreCase("TrueHD"))
+ if (audioFormat == "truehd")
{
return "TrueHD";
}
- if (audioFormat.ContainsIgnoreCase("FLAC"))
+ if (audioFormat == "flac")
{
return "FLAC";
}
- if (audioFormat.ContainsIgnoreCase("DTS"))
+ if (audioFormat == "dts")
{
- if (splitAdditionalFeatures.ContainsIgnoreCase("XLL"))
+ if (audioProfile == "DTS:X")
{
- if (splitAdditionalFeatures.ContainsIgnoreCase("X"))
- {
- return "DTS-X";
- }
+ return "DTS-X";
+ }
+ if (audioProfile == "DTS-HD MA")
+ {
return "DTS-HD MA";
}
- if (splitAdditionalFeatures.ContainsIgnoreCase("ES"))
+ if (audioProfile == "DTS-ES")
{
return "DTS-ES";
}
- if (splitAdditionalFeatures.ContainsIgnoreCase("XBR"))
+ if (audioProfile == "DTS-HD HRA")
{
return "DTS-HD HRA";
}
+ if (audioProfile == "DTS Express")
+ {
+ return "DTS Express";
+ }
+
+ if (audioProfile == "DTS 96/24")
+ {
+ return "DTS 96/24";
+ }
+
return "DTS";
}
- if (audioFormat.ContainsIgnoreCase("E-AC-3"))
+ if (audioCodecID == "ec+3")
{
- if (splitAdditionalFeatures.ContainsIgnoreCase("JOC"))
- {
- return "EAC3 Atmos";
- }
+ return "EAC3 Atmos";
+ }
+ if (audioFormat == "eac3")
+ {
return "EAC3";
}
- if (audioFormat.ContainsIgnoreCase("AC-3"))
+ if (audioFormat == "ac3")
{
return "AC3";
}
- if (audioFormat.ContainsIgnoreCase("AAC"))
+ if (audioFormat == "aac")
{
if (audioCodecID == "A_AAC/MPEG4/LC/SBR")
{
@@ -128,461 +121,166 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
return "AAC";
}
- if (audioFormat.ContainsIgnoreCase("mp3"))
+ if (audioFormat == "mp3")
{
return "MP3";
}
- if (audioFormat.ContainsIgnoreCase("MPEG Audio"))
+ if (audioFormat == "mp2")
{
- if (mediaInfo.AudioCodecID == "55" || mediaInfo.AudioCodecID == "A_MPEG/L3" || mediaInfo.AudioProfile == "Layer 3")
- {
- return "MP3";
- }
-
- if (mediaInfo.AudioCodecID == "A_MPEG/L2" || mediaInfo.AudioProfile == "Layer 2")
- {
- return "MP2";
- }
+ return "MP2";
}
- if (audioFormat.ContainsIgnoreCase("Opus"))
+ if (audioFormat == "opus")
{
return "Opus";
}
- if (audioFormat.ContainsIgnoreCase("PCM"))
+ if (audioFormat.StartsWith("pcm_") || audioFormat.StartsWith("adpcm_"))
{
return "PCM";
}
- if (audioFormat.ContainsIgnoreCase("ADPCM"))
- {
- return "PCM";
- }
-
- if (audioFormat.ContainsIgnoreCase("Vorbis"))
+ if (audioFormat == "vorbis")
{
return "Vorbis";
}
- if (audioFormat.ContainsIgnoreCase("WMA"))
+ if (audioFormat == "wmav1" ||
+ audioFormat == "wmav2")
{
return "WMA";
}
- if (audioFormat.ContainsIgnoreCase("A_QUICKTIME"))
- {
- return "";
- }
-
Logger.Debug()
- .Message("Unknown audio format: '{0}' in '{1}'.", string.Join(", ", mediaInfo.AudioFormat, audioCodecID, audioProfile, audioCodecLibrary, mediaInfo.AudioAdditionalFeatures), sceneName)
- .WriteSentryWarn("UnknownAudioFormat", mediaInfo.ContainerFormat, mediaInfo.AudioFormat, audioCodecID)
+ .Message("Unknown audio format: '{0}' in '{1}'.", mediaInfo.RawStreamData, sceneName)
+ .WriteSentryWarn("UnknownAudioFormatFFProbe", mediaInfo.ContainerFormat, mediaInfo.AudioFormat, audioCodecID)
.Write();
return mediaInfo.AudioFormat;
}
- public static string FormatAudioCodecLegacy(MediaInfoModel mediaInfo, string sceneName)
- {
- var audioFormat = mediaInfo.AudioFormat;
-
- if (audioFormat.IsNullOrWhiteSpace())
- {
- return audioFormat;
- }
-
- if (audioFormat.EqualsIgnoreCase("AC-3"))
- {
- return "AC3";
- }
-
- if (audioFormat.EqualsIgnoreCase("E-AC-3"))
- {
- return "EAC3";
- }
-
- if (audioFormat.EqualsIgnoreCase("AAC"))
- {
- return "AAC";
- }
-
- if (audioFormat.EqualsIgnoreCase("MPEG Audio") && mediaInfo.AudioProfile == "Layer 3")
- {
- return "MP3";
- }
-
- if (audioFormat.EqualsIgnoreCase("DTS"))
- {
- return "DTS";
- }
-
- if (audioFormat.EqualsIgnoreCase("TrueHD"))
- {
- return "TrueHD";
- }
-
- if (audioFormat.EqualsIgnoreCase("FLAC"))
- {
- return "FLAC";
- }
-
- if (audioFormat.EqualsIgnoreCase("Vorbis"))
- {
- return "Vorbis";
- }
-
- if (audioFormat.EqualsIgnoreCase("Opus"))
- {
- return "Opus";
- }
-
- return audioFormat;
- }
-
public static string FormatVideoCodec(MediaInfoModel mediaInfo, string sceneName)
{
if (mediaInfo.VideoFormat == null)
{
- return FormatVideoCodecLegacy(mediaInfo, sceneName);
+ return null;
}
- var videoFormat = mediaInfo.VideoFormat.Trim().Split(new[] { " / " }, StringSplitOptions.RemoveEmptyEntries);
+ var videoFormat = mediaInfo.VideoFormat;
var videoCodecID = mediaInfo.VideoCodecID ?? string.Empty;
- var videoProfile = mediaInfo.VideoProfile ?? string.Empty;
- var videoCodecLibrary = mediaInfo.VideoCodecLibrary ?? string.Empty;
- var result = mediaInfo.VideoFormat.Trim();
+ var result = videoFormat.Trim();
if (videoFormat.Empty())
{
return result;
}
- if (videoFormat.ContainsIgnoreCase("x264"))
+ // see definitions here: https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/codec_desc.c
+ if (videoCodecID == "x264")
{
return "x264";
}
- if (videoFormat.ContainsIgnoreCase("AVC") || videoFormat.ContainsIgnoreCase("V.MPEG4/ISO/AVC"))
+ if (videoFormat == "h264")
{
- if (videoCodecLibrary.StartsWithIgnoreCase("x264"))
- {
- return "x264";
- }
-
return GetSceneNameMatch(sceneName, "AVC", "x264", "h264");
}
- if (videoFormat.ContainsIgnoreCase("HEVC") || videoFormat.ContainsIgnoreCase("V_MPEGH/ISO/HEVC"))
+ if (videoCodecID == "x265")
{
- if (videoCodecLibrary.StartsWithIgnoreCase("x265"))
- {
- return "x265";
- }
+ return "x265";
+ }
+ if (videoFormat == "hevc")
+ {
return GetSceneNameMatch(sceneName, "HEVC", "x265", "h265");
}
- if (videoFormat.ContainsIgnoreCase("MPEG Video"))
- {
- if (videoCodecID == "2" || videoCodecID == "V_MPEG2")
- {
- return "MPEG2";
- }
-
- if (videoCodecID.IsNullOrWhiteSpace())
- {
- return "MPEG";
- }
- }
-
- if (videoFormat.ContainsIgnoreCase("MPEG-2 Video"))
+ if (videoFormat == "mpeg2video")
{
return "MPEG2";
}
- if (videoFormat.ContainsIgnoreCase("MPEG-4 Visual"))
+ if (videoFormat == "mpeg1video")
{
- if (videoCodecID.ContainsIgnoreCase("XVID") ||
- videoCodecLibrary.StartsWithIgnoreCase("XviD"))
+ return "MPEG";
+ }
+
+ if (videoFormat == "mpeg4")
+ {
+ if (videoCodecID == "XVID")
{
return "XviD";
}
- if (videoCodecID.ContainsIgnoreCase("DIV3") ||
- videoCodecID.ContainsIgnoreCase("DIVX") ||
- videoCodecID.ContainsIgnoreCase("DX50") ||
- videoCodecLibrary.StartsWithIgnoreCase("DivX"))
+ if (videoCodecID == "DIV3" ||
+ videoCodecID == "DIVX" ||
+ videoCodecID == "DX50")
{
return "DivX";
}
+
+ return "";
}
- if (videoFormat.ContainsIgnoreCase("MPEG-4 Visual") || videoFormat.ContainsIgnoreCase("mp4v"))
- {
- result = GetSceneNameMatch(sceneName, "XviD", "DivX", "");
- if (result.IsNotNullOrWhiteSpace())
- {
- return result;
- }
-
- if (videoCodecLibrary.Contains("Lavc"))
- {
- return ""; // libavcodec mpeg-4
- }
-
- if (videoCodecLibrary.Contains("em4v"))
- {
- return ""; // NeroDigital
- }
-
- if (videoCodecLibrary.Contains("Intel(R) IPP"))
- {
- return ""; // Intel(R) IPP
- }
-
- if (videoCodecLibrary.Contains("ZJMedia") ||
- videoCodecLibrary.Contains("DigiArty"))
- {
- return ""; // Other
- }
-
- if (string.IsNullOrEmpty(videoCodecLibrary))
- {
- return ""; // Unknown mp4v
- }
- }
-
- if (videoFormat.ContainsIgnoreCase("VC-1"))
+ if (videoFormat == "vc1")
{
return "VC1";
}
- if (videoFormat.ContainsIgnoreCase("AV1"))
+ if (videoFormat == "av1")
{
return "AV1";
}
- if (videoFormat.ContainsIgnoreCase("VP6") || videoFormat.ContainsIgnoreCase("VP7") ||
- videoFormat.ContainsIgnoreCase("VP8") || videoFormat.ContainsIgnoreCase("VP9"))
+ if (videoFormat == "vp6" ||
+ videoFormat == "vp7" ||
+ videoFormat == "vp8" ||
+ videoFormat == "vp9")
{
- return videoFormat.First().ToUpperInvariant();
+ return videoFormat.ToUpperInvariant();
}
- if (videoFormat.ContainsIgnoreCase("WMV1") || videoFormat.ContainsIgnoreCase("WMV2"))
+ if (videoFormat == "wmv1" ||
+ videoFormat == "wmv2")
{
return "WMV";
}
- if (videoFormat.ContainsIgnoreCase("DivX") || videoFormat.ContainsIgnoreCase("div3"))
+ if (videoFormat == "qtrle" ||
+ videoFormat == "rpza" ||
+ videoFormat == "rv10" ||
+ videoFormat == "rv20" ||
+ videoFormat == "rv30" ||
+ videoFormat == "rv40")
{
- return "DivX";
- }
-
- if (videoFormat.ContainsIgnoreCase("XviD"))
- {
- return "XviD";
- }
-
- if (videoFormat.ContainsIgnoreCase("V_QUICKTIME") ||
- videoFormat.ContainsIgnoreCase("RealVideo 4"))
- {
- return "";
- }
-
- if (videoFormat.ContainsIgnoreCase("mp42") ||
- videoFormat.ContainsIgnoreCase("mp43"))
- {
- // MS old DivX competitor
return "";
}
Logger.Debug()
- .Message("Unknown video format: '{0}' in '{1}'.", string.Join(", ", mediaInfo.VideoFormat, videoCodecID, videoProfile, videoCodecLibrary), sceneName)
- .WriteSentryWarn("UnknownVideoFormat", mediaInfo.ContainerFormat, mediaInfo.VideoFormat, videoCodecID)
+ .Message("Unknown video format: '{0}' in '{1}'.", mediaInfo.RawStreamData, sceneName)
+ .WriteSentryWarn("UnknownVideoFormatFFProbe", mediaInfo.ContainerFormat, videoFormat, videoCodecID)
.Write();
return result;
}
- public static string FormatVideoCodecLegacy(MediaInfoModel mediaInfo, string sceneName)
- {
- var videoCodec = mediaInfo.VideoCodec;
-
- if (videoCodec.IsNullOrWhiteSpace())
- {
- return videoCodec;
- }
-
- if (videoCodec == "AVC")
- {
- return GetSceneNameMatch(sceneName, "AVC", "h264", "x264");
- }
-
- if (videoCodec == "V_MPEGH/ISO/HEVC" || videoCodec == "HEVC")
- {
- return GetSceneNameMatch(sceneName, "HEVC", "h265", "x265");
- }
-
- if (videoCodec == "MPEG-2 Video")
- {
- return "MPEG2";
- }
-
- if (videoCodec == "MPEG-4 Visual")
- {
- return GetSceneNameMatch(sceneName, "DivX", "XviD");
- }
-
- if (videoCodec.StartsWithIgnoreCase("XviD"))
- {
- return "XviD";
- }
-
- if (videoCodec.StartsWithIgnoreCase("DivX"))
- {
- return "DivX";
- }
-
- if (videoCodec.EqualsIgnoreCase("VC-1"))
- {
- return "VC1";
- }
-
- return videoCodec;
- }
-
private static decimal? FormatAudioChannelsFromAudioChannelPositions(MediaInfoModel mediaInfo)
{
- var audioChannelPositions = mediaInfo.AudioChannelPositions;
- var audioFormat = mediaInfo.AudioFormat;
-
- if (audioChannelPositions.IsNullOrWhiteSpace())
+ if (mediaInfo.AudioChannelPositions == null)
{
- return null;
+ return 0;
}
- try
+ var match = PositionRegex.Match(mediaInfo.AudioChannelPositions);
+ if (match.Success)
{
- if (audioChannelPositions.Contains("+"))
- {
- return audioChannelPositions.Split('+')
- .Sum(s => decimal.Parse(s.Trim(), CultureInfo.InvariantCulture));
- }
-
- if (audioChannelPositions.Contains("/"))
- {
- var channelStringList = Regex.Replace(audioChannelPositions,
- @"^\d+\sobjects",
- "",
- RegexOptions.Compiled | RegexOptions.IgnoreCase)
- .Replace("Object Based / ", "")
- .Split(new string[] { " / " }, StringSplitOptions.RemoveEmptyEntries)
- .FirstOrDefault()
- ?.Split('/');
-
- var positions = default(decimal);
-
- if (channelStringList == null)
- {
- return 0;
- }
-
- foreach (var channel in channelStringList)
- {
- var channelSplit = channel.Split(new string[] { "." }, StringSplitOptions.None);
-
- if (channelSplit.Length == 3)
- {
- positions += decimal.Parse(string.Format("{0}.{1}", channelSplit[1], channelSplit[2]), CultureInfo.InvariantCulture);
- }
- else
- {
- positions += decimal.Parse(channel, CultureInfo.InvariantCulture);
- }
- }
-
- return positions;
- }
- }
- catch (Exception ex)
- {
- Logger.Warn()
- .Message("Unable to format audio channels using 'AudioChannelPositions', with a value of: '{0}' and '{1}'. Error {2}", audioChannelPositions, mediaInfo.AudioChannelPositionsTextContainer, ex.Message)
- .WriteSentryWarn("UnknownAudioChannelFormat", audioChannelPositions, mediaInfo.AudioChannelPositionsTextContainer)
- .Write();
+ return decimal.Parse(match.Groups["position"].Value, NumberStyles.Number, CultureInfo.InvariantCulture);
}
- return null;
- }
-
- private static decimal? FormatAudioChannelsFromAudioChannelPositionsText(MediaInfoModel mediaInfo)
- {
- var audioChannelPositionsTextContainer = mediaInfo.AudioChannelPositionsTextContainer;
- var audioChannelPositionsTextStream = mediaInfo.AudioChannelPositionsTextStream;
- var audioChannelsContainer = mediaInfo.AudioChannelsContainer;
- var audioChannelsStream = mediaInfo.AudioChannelsStream;
-
- //Skip if the positions texts give us nothing
- if ((audioChannelPositionsTextContainer.IsNullOrWhiteSpace() || audioChannelPositionsTextContainer == "Object Based") &&
- (audioChannelPositionsTextStream.IsNullOrWhiteSpace() || audioChannelPositionsTextStream == "Object Based"))
- {
- return null;
- }
-
- try
- {
- if (audioChannelsStream > 0)
- {
- return audioChannelPositionsTextStream.ContainsIgnoreCase("LFE") ? audioChannelsStream - 1 + 0.1m : audioChannelsStream;
- }
-
- return audioChannelPositionsTextContainer.ContainsIgnoreCase("LFE") ? audioChannelsContainer - 1 + 0.1m : audioChannelsContainer;
- }
- catch (Exception e)
- {
- Logger.Warn(e, "Unable to format audio channels using 'AudioChannelPositionsText' or 'AudioChannelPositionsTextStream', with value of: '{0}' and '{1}", audioChannelPositionsTextContainer, audioChannelPositionsTextStream);
- }
-
- return null;
- }
-
- private static decimal? FormatAudioChannelsFromAudioChannels(MediaInfoModel mediaInfo)
- {
- var audioChannelsContainer = mediaInfo.AudioChannelsContainer;
- var audioChannelsStream = mediaInfo.AudioChannelsStream;
-
- var audioFormat = (mediaInfo.AudioFormat ?? string.Empty).Trim().Split(new[] { " / " }, StringSplitOptions.RemoveEmptyEntries);
- var splitAdditionalFeatures = (mediaInfo.AudioAdditionalFeatures ?? string.Empty).Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
-
- // Workaround https://github.com/MediaArea/MediaInfo/issues/299 for DTS-X Audio
- if (audioFormat.ContainsIgnoreCase("DTS") &&
- splitAdditionalFeatures.ContainsIgnoreCase("XLL") &&
- splitAdditionalFeatures.ContainsIgnoreCase("X") &&
- audioChannelsContainer > 0)
- {
- return audioChannelsContainer - 1 + 0.1m;
- }
-
- // FLAC 6 channels is likely 5.1
- if (audioFormat.ContainsIgnoreCase("FLAC") && audioChannelsContainer == 6)
- {
- return 5.1m;
- }
-
- if (mediaInfo.SchemaRevision > 5)
- {
- return audioChannelsStream > 0 ? audioChannelsStream : audioChannelsContainer;
- }
-
- if (mediaInfo.SchemaRevision >= 3)
- {
- return audioChannelsContainer;
- }
-
- return null;
+ return 0;
}
private static string GetSceneNameMatch(string sceneName, params string[] tokens)
@@ -603,23 +301,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
public static string FormatVideoDynamicRange(MediaInfoModel mediaInfo)
{
- if (mediaInfo.VideoHdrFormat.IsNotNullOrWhiteSpace())
- {
- return VideoDynamicRangeHdr;
- }
-
- if (mediaInfo.VideoBitDepth >= 10 &&
- mediaInfo.VideoColourPrimaries.IsNotNullOrWhiteSpace() &&
- mediaInfo.VideoTransferCharacteristics.IsNotNullOrWhiteSpace())
- {
- if (mediaInfo.VideoColourPrimaries.EqualsIgnoreCase(ValidHdrColourPrimaries) &&
- ValidHdrTransferFunctions.Any(mediaInfo.VideoTransferCharacteristics.Contains))
- {
- return VideoDynamicRangeHdr;
- }
- }
-
- return "";
+ return mediaInfo.VideoHdrFormat != HdrFormat.None ? VideoDynamicRangeHdr : "";
}
}
}
diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoLib.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoLib.cs
deleted file mode 100644
index 36548c047..000000000
--- a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoLib.cs
+++ /dev/null
@@ -1,370 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Runtime.InteropServices;
-using System.Text;
-using NLog;
-using NzbDrone.Common.Extensions;
-using NzbDrone.Common.Instrumentation;
-
-namespace NzbDrone.Core.MediaFiles.MediaInfo
-{
- [Flags]
- public enum BufferStatus
- {
- Accepted = 1,
- Filled = 2,
- Updated = 4,
- Finalized = 8
- }
-
- public enum StreamKind
- {
- General,
- Video,
- Audio,
- Text,
- Other,
- Image,
- Menu
- }
-
- public enum InfoKind
- {
- Name,
- Text,
- Measure,
- Options,
- NameText,
- MeasureText,
- Info,
- HowTo
- }
-
- public enum InfoOptions
- {
- ShowInInform,
- Support,
- ShowInSupported,
- TypeOfValue
- }
-
- public enum InfoFileOptions
- {
- FileOption_Nothing = 0x00,
- FileOption_NoRecursive = 0x01,
- FileOption_CloseAll = 0x02,
- FileOption_Max = 0x04
- }
-
- public class MediaInfo : IDisposable
- {
- private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(MediaInfo));
- private IntPtr _handle;
-
- public bool MustUseAnsi { get; set; }
- public Encoding Encoding { get; set; }
-
- public MediaInfo()
- {
- _handle = MediaInfo_New();
-
- InitializeEncoding();
- }
-
- ~MediaInfo()
- {
- if (_handle != IntPtr.Zero)
- {
- MediaInfo_Delete(_handle);
- }
- }
-
- public void Dispose()
- {
- if (_handle != IntPtr.Zero)
- {
- MediaInfo_Delete(_handle);
- }
-
- GC.SuppressFinalize(this);
- }
-
- private void InitializeEncoding()
- {
- if (Environment.OSVersion.ToString().IndexOf("Windows") != -1)
- {
- // Windows guaranteed UCS-2
- MustUseAnsi = false;
- Encoding = Encoding.Unicode;
- }
- else
- {
- var responses = new List();
-
- // Linux normally UCS-4. As fallback we try UCS-2 and plain Ansi.
- MustUseAnsi = false;
- Encoding = Encoding.UTF32;
-
- var version = Option("Info_Version", "");
- responses.Add(version);
- if (version.StartsWith("MediaInfoLib"))
- {
- return;
- }
-
- Encoding = Encoding.Unicode;
-
- version = Option("Info_Version", "");
- responses.Add(version);
- if (version.StartsWith("MediaInfoLib"))
- {
- return;
- }
-
- MustUseAnsi = true;
- Encoding = Encoding.Default;
-
- version = Option("Info_Version", "");
- responses.Add(version);
- if (version.StartsWith("MediaInfoLib"))
- {
- return;
- }
-
- throw new NotSupportedException("Unsupported MediaInfoLib encoding, version check responses (may be gibberish, show it to the Radarr devs): " + responses.Join(", "));
- }
- }
-
- private IntPtr MakeStringParameter(string value)
- {
- var buffer = Encoding.GetBytes(value);
-
- Array.Resize(ref buffer, buffer.Length + 4);
-
- var buf = Marshal.AllocHGlobal(buffer.Length);
- Marshal.Copy(buffer, 0, buf, buffer.Length);
-
- return buf;
- }
-
- private string MakeStringResult(IntPtr value)
- {
- if (Encoding == Encoding.Unicode)
- {
- return Marshal.PtrToStringUni(value);
- }
- else if (Encoding == Encoding.UTF32)
- {
- int i = 0;
- for (; i < 1024; i += 4)
- {
- var data = Marshal.ReadInt32(value, i);
- if (data == 0)
- {
- break;
- }
- }
-
- var buffer = new byte[i];
- Marshal.Copy(value, buffer, 0, i);
-
- return Encoding.GetString(buffer, 0, i);
- }
- else
- {
- return Marshal.PtrToStringAnsi(value);
- }
- }
-
- public int Open(string fileName)
- {
- var pFileName = MakeStringParameter(fileName);
- try
- {
- if (MustUseAnsi)
- {
- return (int)MediaInfoA_Open(_handle, pFileName);
- }
- else
- {
- return (int)MediaInfo_Open(_handle, pFileName);
- }
- }
- finally
- {
- Marshal.FreeHGlobal(pFileName);
- }
- }
-
- public int Open(Stream stream)
- {
- if (stream.Length < 1024)
- {
- return 0;
- }
-
- var isValid = (int)MediaInfo_Open_Buffer_Init(_handle, stream.Length, 0);
- if (isValid == 1)
- {
- var buffer = new byte[16 * 1024];
- long seekStart = 0;
- long totalRead = 0;
- int bufferRead;
-
- do
- {
- bufferRead = stream.Read(buffer, 0, buffer.Length);
- totalRead += bufferRead;
-
- var status = (BufferStatus)MediaInfo_Open_Buffer_Continue(_handle, buffer, (IntPtr)bufferRead);
-
- if (status.HasFlag(BufferStatus.Finalized) || status <= 0 || bufferRead == 0)
- {
- Logger.Trace("Read file offset {0}-{1} ({2} bytes)", seekStart, stream.Position, stream.Position - seekStart);
- break;
- }
-
- var seekPos = MediaInfo_Open_Buffer_Continue_GoTo_Get(_handle);
- if (seekPos != -1)
- {
- Logger.Trace("Read file offset {0}-{1} ({2} bytes)", seekStart, stream.Position, stream.Position - seekStart);
- seekPos = stream.Seek(seekPos, SeekOrigin.Begin);
- seekStart = seekPos;
- MediaInfo_Open_Buffer_Init(_handle, stream.Length, seekPos);
- }
- }
- while (bufferRead > 0);
-
- MediaInfo_Open_Buffer_Finalize(_handle);
-
- Logger.Trace("Read a total of {0} bytes ({1:0.0}%)", totalRead, totalRead * 100.0 / stream.Length);
- }
-
- return isValid;
- }
-
- public void Close()
- {
- MediaInfo_Close(_handle);
- }
-
- public string Get(StreamKind streamKind, int streamNumber, string parameter, InfoKind infoKind = InfoKind.Text, InfoKind searchKind = InfoKind.Name)
- {
- var pParameter = MakeStringParameter(parameter);
- try
- {
- if (MustUseAnsi)
- {
- return MakeStringResult(MediaInfoA_Get(_handle, (IntPtr)streamKind, (IntPtr)streamNumber, pParameter, (IntPtr)infoKind, (IntPtr)searchKind));
- }
- else
- {
- return MakeStringResult(MediaInfo_Get(_handle, (IntPtr)streamKind, (IntPtr)streamNumber, pParameter, (IntPtr)infoKind, (IntPtr)searchKind));
- }
- }
- finally
- {
- Marshal.FreeHGlobal(pParameter);
- }
- }
-
- public string Get(StreamKind streamKind, int streamNumber, int parameter, InfoKind infoKind)
- {
- if (MustUseAnsi)
- {
- return MakeStringResult(MediaInfoA_GetI(_handle, (IntPtr)streamKind, (IntPtr)streamNumber, (IntPtr)parameter, (IntPtr)infoKind));
- }
- else
- {
- return MakeStringResult(MediaInfo_GetI(_handle, (IntPtr)streamKind, (IntPtr)streamNumber, (IntPtr)parameter, (IntPtr)infoKind));
- }
- }
-
- public string Option(string option, string value)
- {
- var pOption = MakeStringParameter(option.ToLowerInvariant());
- var pValue = MakeStringParameter(value);
- try
- {
- if (MustUseAnsi)
- {
- return MakeStringResult(MediaInfoA_Option(_handle, pOption, pValue));
- }
- else
- {
- return MakeStringResult(MediaInfo_Option(_handle, pOption, pValue));
- }
- }
- finally
- {
- Marshal.FreeHGlobal(pOption);
- Marshal.FreeHGlobal(pValue);
- }
- }
-
- public int State_Get()
- {
- return (int)MediaInfo_State_Get(_handle);
- }
-
- public int Count_Get(StreamKind streamKind, int streamNumber = -1)
- {
- return (int)MediaInfo_Count_Get(_handle, (IntPtr)streamKind, (IntPtr)streamNumber);
- }
-
- [DllImport("mediainfo")]
- private static extern IntPtr MediaInfo_New();
- [DllImport("mediainfo")]
- private static extern void MediaInfo_Delete(IntPtr handle);
- [DllImport("mediainfo")]
- private static extern IntPtr MediaInfo_Open(IntPtr handle, IntPtr fileName);
- [DllImport("mediainfo")]
- private static extern IntPtr MediaInfo_Open_Buffer_Init(IntPtr handle, long fileSize, long fileOffset);
- [DllImport("mediainfo")]
- private static extern IntPtr MediaInfo_Open_Buffer_Continue(IntPtr handle, byte[] buffer, IntPtr bufferSize);
- [DllImport("mediainfo")]
- private static extern long MediaInfo_Open_Buffer_Continue_GoTo_Get(IntPtr handle);
- [DllImport("mediainfo")]
- private static extern IntPtr MediaInfo_Open_Buffer_Finalize(IntPtr handle);
- [DllImport("mediainfo")]
- private static extern void MediaInfo_Close(IntPtr handle);
- [DllImport("mediainfo")]
- private static extern IntPtr MediaInfo_GetI(IntPtr handle, IntPtr streamKind, IntPtr streamNumber, IntPtr parameter, IntPtr infoKind);
- [DllImport("mediainfo")]
- private static extern IntPtr MediaInfo_Get(IntPtr handle, IntPtr streamKind, IntPtr streamNumber, IntPtr parameter, IntPtr infoKind, IntPtr searchKind);
- [DllImport("mediainfo")]
- private static extern IntPtr MediaInfo_Option(IntPtr handle, IntPtr option, IntPtr value);
- [DllImport("mediainfo")]
- private static extern IntPtr MediaInfo_State_Get(IntPtr handle);
- [DllImport("mediainfo")]
- private static extern IntPtr MediaInfo_Count_Get(IntPtr handle, IntPtr streamKind, IntPtr streamNumber);
-
- [DllImport("mediainfo")]
- private static extern IntPtr MediaInfoA_New();
- [DllImport("mediainfo")]
- private static extern void MediaInfoA_Delete(IntPtr handle);
- [DllImport("mediainfo")]
- private static extern IntPtr MediaInfoA_Open(IntPtr handle, IntPtr fileName);
- [DllImport("mediainfo")]
- private static extern IntPtr MediaInfoA_Open_Buffer_Init(IntPtr handle, long fileSize, long fileOffset);
- [DllImport("mediainfo")]
- private static extern IntPtr MediaInfoA_Open_Buffer_Continue(IntPtr handle, byte[] buffer, IntPtr bufferSize);
- [DllImport("mediainfo")]
- private static extern long MediaInfoA_Open_Buffer_Continue_GoTo_Get(IntPtr handle);
- [DllImport("mediainfo")]
- private static extern IntPtr MediaInfoA_Open_Buffer_Finalize(IntPtr handle);
- [DllImport("mediainfo")]
- private static extern void MediaInfoA_Close(IntPtr handle);
- [DllImport("mediainfo")]
- private static extern IntPtr MediaInfoA_GetI(IntPtr handle, IntPtr streamKind, IntPtr streamNumber, IntPtr parameter, IntPtr infoKind);
- [DllImport("mediainfo")]
- private static extern IntPtr MediaInfoA_Get(IntPtr handle, IntPtr streamKind, IntPtr streamNumber, IntPtr parameter, IntPtr infoKind, IntPtr searchKind);
- [DllImport("mediainfo")]
- private static extern IntPtr MediaInfoA_Option(IntPtr handle, IntPtr option, IntPtr value);
- [DllImport("mediainfo")]
- private static extern IntPtr MediaInfoA_State_Get(IntPtr handle);
- [DllImport("mediainfo")]
- private static extern IntPtr MediaInfoA_Count_Get(IntPtr handle, IntPtr streamKind, IntPtr streamNumber);
- }
-}
diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs
index 0f398de99..723cbdcf3 100644
--- a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs
+++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs
@@ -1,44 +1,70 @@
using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using FFMpegCore;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.MediaFiles.MediaInfo
{
public class MediaInfoModel : IEmbeddedDocument
{
- public string ContainerFormat { get; set; }
-
- // Deprecated according to MediaInfo
- public string VideoCodec { get; set; }
- public string VideoFormat { get; set; }
- public string VideoCodecID { get; set; }
- public string VideoProfile { get; set; }
- public string VideoCodecLibrary { get; set; }
- public int VideoBitrate { get; set; }
- public int VideoBitDepth { get; set; }
- public int VideoMultiViewCount { get; set; }
- public string VideoColourPrimaries { get; set; }
- public string VideoTransferCharacteristics { get; set; }
- public string VideoHdrFormat { get; set; }
- public string VideoHdrFormatCompatibility { get; set; }
- public int Width { get; set; }
- public int Height { get; set; }
- public string AudioFormat { get; set; }
- public string AudioCodecID { get; set; }
- public string AudioCodecLibrary { get; set; }
- public string AudioAdditionalFeatures { get; set; }
- public int AudioBitrate { get; set; }
- public TimeSpan RunTime { get; set; }
- public int AudioStreamCount { get; set; }
- public int AudioChannelsContainer { get; set; }
- public int AudioChannelsStream { get; set; }
- public string AudioChannelPositions { get; set; }
- public string AudioChannelPositionsTextContainer { get; set; }
- public string AudioChannelPositionsTextStream { 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; }
+ public string RawStreamData { get; set; }
+ public string RawFrameData { get; set; }
public int SchemaRevision { get; set; }
+
+ [JsonIgnore]
+ public IMediaAnalysis Analysis => FFProbe.Analyse(RawStreamData);
+
+ [JsonIgnore]
+ public IMediaAnalysis Frames => FFProbe.Analyse(RawFrameData);
+
+ public string ContainerFormat { get; set; }
+ public string VideoFormat { get; set; }
+
+ public string VideoCodecID { get; set; }
+
+ public string VideoProfile { get; set; }
+
+ public int VideoBitrate { get; set; }
+
+ public int VideoBitDepth { get; set; }
+
+ public int VideoMultiViewCount { get; set; }
+
+ public string VideoColourPrimaries { get; set; }
+
+ public string VideoTransferCharacteristics { get; set; }
+
+ public DoviConfigurationRecordSideData DoviConfigurationRecord { get; set; }
+
+ public HdrFormat VideoHdrFormat { get; set; }
+
+ public int Height { get; set; }
+
+ public int Width { get; set; }
+
+ public string AudioFormat { get; set; }
+
+ public string AudioCodecID { get; set; }
+
+ public string AudioProfile { get; set; }
+
+ public int AudioBitrate { get; set; }
+
+ public TimeSpan RunTime { get; set; }
+
+ public int AudioStreamCount { get; set; }
+
+ public int AudioChannels { get; set; }
+
+ public string AudioChannelPositions { get; set; }
+
+ public decimal VideoFps { get; set; }
+
+ public List AudioLanguages { get; set; }
+
+ public List Subtitles { get; set; }
+
+ public string ScanType { get; set; }
}
}
diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs
index 0674c953a..1bca63e54 100644
--- a/src/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs
+++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs
@@ -1,6 +1,8 @@
using System;
-using System.Globalization;
+using System.Collections.Generic;
using System.IO;
+using System.Linq;
+using FFMpegCore;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
@@ -17,14 +19,33 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
{
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger;
+ private readonly List _pixelFormats;
- public const int MINIMUM_MEDIA_INFO_SCHEMA_REVISION = 4;
- public const int CURRENT_MEDIA_INFO_SCHEMA_REVISION = 7;
+ public const int MINIMUM_MEDIA_INFO_SCHEMA_REVISION = 8;
+ public const int CURRENT_MEDIA_INFO_SCHEMA_REVISION = 8;
+
+ private static readonly string[] ValidHdrColourPrimaries = { "bt2020" };
+ private static readonly string[] HlgTransferFunctions = { "bt2020-10", "arib-std-b67" };
+ private static readonly string[] PqTransferFunctions = { "smpte2084" };
+ private static readonly string[] ValidHdrTransferFunctions = HlgTransferFunctions.Concat(PqTransferFunctions).ToArray();
public VideoFileInfoReader(IDiskProvider diskProvider, Logger logger)
{
_diskProvider = diskProvider;
_logger = logger;
+
+ // We bundle ffprobe for all platforms
+ GlobalFFOptions.Configure(options => options.BinaryFolder = AppDomain.CurrentDomain.BaseDirectory);
+
+ try
+ {
+ _pixelFormats = FFProbe.GetPixelFormats();
+ }
+ catch (Exception e)
+ {
+ _logger.Error(e, "Failed to get supported pixel formats from ffprobe");
+ _pixelFormats = new List();
+ }
}
public MediaInfoModel GetMediaInfo(string filename)
@@ -34,181 +55,77 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
throw new FileNotFoundException("Media file does not exist: " + filename);
}
- MediaInfo mediaInfo = null;
-
// TODO: Cache media info by path, mtime and length so we don't need to read files multiple times
try
{
- mediaInfo = new MediaInfo();
_logger.Debug("Getting media info from {0}", filename);
+ var ffprobeOutput = FFProbe.GetStreamJson(filename, ffOptions: new FFOptions { ExtraArguments = "-probesize 50000000" });
- if (filename.ToLower().EndsWith(".ts"))
+ var analysis = FFProbe.AnalyseStreamJson(ffprobeOutput);
+
+ if (analysis.PrimaryAudioStream.ChannelLayout.IsNullOrWhiteSpace())
{
- // For .ts files we often have to scan more of the file to get all the info we need
- mediaInfo.Option("ParseSpeed", "0.3");
- }
- else
- {
- mediaInfo.Option("ParseSpeed", "0.0");
+ ffprobeOutput = FFProbe.GetStreamJson(filename, ffOptions: new FFOptions { ExtraArguments = "-probesize 150000000 -analyzeduration 150000000" });
+ analysis = FFProbe.AnalyseStreamJson(ffprobeOutput);
}
- int open;
-
- using (var stream = _diskProvider.OpenReadStream(filename))
+ var mediaInfoModel = new MediaInfoModel
{
- open = mediaInfo.Open(stream);
+ ContainerFormat = analysis.Format.FormatName,
+ VideoFormat = analysis.PrimaryVideoStream?.CodecName,
+ VideoCodecID = analysis.PrimaryVideoStream?.CodecTagString,
+ VideoProfile = analysis.PrimaryVideoStream?.Profile,
+ VideoBitrate = analysis.PrimaryVideoStream?.BitRate ?? 0,
+ VideoMultiViewCount = 1,
+ VideoBitDepth = GetPixelFormat(analysis.PrimaryVideoStream?.PixelFormat).Components.Min(x => x.BitDepth),
+ VideoColourPrimaries = analysis.PrimaryVideoStream?.ColorPrimaries,
+ VideoTransferCharacteristics = analysis.PrimaryVideoStream?.ColorTransfer,
+ DoviConfigurationRecord = analysis.PrimaryVideoStream?.SideDataList?.Find(x => x.GetType().Name == nameof(DoviConfigurationRecordSideData)) as DoviConfigurationRecordSideData,
+ Height = analysis.PrimaryVideoStream?.Height ?? 0,
+ Width = analysis.PrimaryVideoStream?.Width ?? 0,
+ AudioFormat = analysis.PrimaryAudioStream?.CodecName,
+ AudioCodecID = analysis.PrimaryAudioStream?.CodecTagString,
+ AudioProfile = analysis.PrimaryAudioStream?.Profile,
+ AudioBitrate = analysis.PrimaryAudioStream?.BitRate ?? 0,
+ RunTime = GetBestRuntime(analysis.PrimaryAudioStream?.Duration, analysis.PrimaryVideoStream.Duration, analysis.Format.Duration),
+ AudioStreamCount = analysis.AudioStreams.Count,
+ AudioChannels = analysis.PrimaryAudioStream?.Channels ?? 0,
+ AudioChannelPositions = analysis.PrimaryAudioStream?.ChannelLayout,
+ VideoFps = analysis.PrimaryVideoStream?.FrameRate ?? 0,
+ AudioLanguages = analysis.AudioStreams?.Select(x => x.Language)
+ .Where(l => l.IsNotNullOrWhiteSpace())
+ .ToList(),
+ Subtitles = analysis.SubtitleStreams?.Select(x => x.Language)
+ .Where(l => l.IsNotNullOrWhiteSpace())
+ .ToList(),
+ ScanType = "Progressive",
+ RawStreamData = ffprobeOutput,
+ SchemaRevision = CURRENT_MEDIA_INFO_SCHEMA_REVISION
+ };
+
+ FFProbeFrames frames = null;
+
+ // if it looks like PQ10 or similar HDR, do a frame analysis to figure out which type it is
+ if (PqTransferFunctions.Contains(mediaInfoModel.VideoTransferCharacteristics))
+ {
+ var frameOutput = FFProbe.GetFrameJson(filename, ffOptions: new () { ExtraArguments = "-read_intervals \"%+#1\" -select_streams v" });
+ mediaInfoModel.RawFrameData = frameOutput;
+
+ frames = FFProbe.AnalyseFrameJson(frameOutput);
}
- if (open != 0)
- {
- int audioRuntime;
- int videoRuntime;
- int generalRuntime;
+ var streamSideData = analysis.PrimaryVideoStream?.SideDataList ?? new ();
+ var framesSideData = frames?.Frames[0]?.SideDataList ?? new ();
- // Runtime
- int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "PlayTime"), out videoRuntime);
- int.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "PlayTime"), out audioRuntime);
- int.TryParse(mediaInfo.Get(StreamKind.General, 0, "PlayTime"), out generalRuntime);
+ var sideData = streamSideData.Concat(framesSideData).ToList();
+ mediaInfoModel.VideoHdrFormat = GetHdrFormat(mediaInfoModel.VideoBitDepth, mediaInfoModel.VideoColourPrimaries, mediaInfoModel.VideoTransferCharacteristics, sideData);
- // Audio Channels
- var audioChannelsStr = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
- var audioChannelPositions = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions/String2");
- int.TryParse(audioChannelsStr, out var audioChannels);
-
- if (audioRuntime == 0 && videoRuntime == 0 && generalRuntime == 0)
- {
- // No runtime, ask mediainfo to scan the whole file
- _logger.Trace("No runtime value found, rescanning at 1.0 scan depth");
- mediaInfo.Option("ParseSpeed", "1.0");
-
- using (var stream = _diskProvider.OpenReadStream(filename))
- {
- open = mediaInfo.Open(stream);
- }
- }
- else if (audioChannels > 2 && audioChannelPositions.IsNullOrWhiteSpace())
- {
- // Some files with DTS don't have ChannelPositions unless more of the file is scanned
- _logger.Trace("DTS audio without expected channel information, rescanning at 0.3 scan depth");
- mediaInfo.Option("ParseSpeed", "0.3");
-
- using (var stream = _diskProvider.OpenReadStream(filename))
- {
- open = mediaInfo.Open(stream);
- }
- }
- }
-
- if (open != 0)
- {
- int width;
- int height;
- int videoBitRate;
- int audioBitRate;
- int audioRuntime;
- int videoRuntime;
- int generalRuntime;
- int streamCount;
- int audioChannels;
- int audioChannelsOrig;
- int videoBitDepth;
- decimal videoFrameRate;
- int videoMultiViewCount;
-
- string subtitles = mediaInfo.Get(StreamKind.General, 0, "Text_Language_List");
- string scanType = mediaInfo.Get(StreamKind.Video, 0, "ScanType");
- int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "Width"), out width);
- int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "Height"), out height);
- int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitRate"), out videoBitRate);
- if (videoBitRate <= 0)
- {
- int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitRate_Nominal"), out videoBitRate);
- }
-
- decimal.TryParse(mediaInfo.Get(StreamKind.Video, 0, "FrameRate"), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out videoFrameRate);
- int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitDepth"), out videoBitDepth);
- int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "MultiView_Count"), out videoMultiViewCount);
-
- //Runtime
- int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "PlayTime"), out videoRuntime);
- int.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "PlayTime"), out audioRuntime);
- int.TryParse(mediaInfo.Get(StreamKind.General, 0, "PlayTime"), out generalRuntime);
-
- string aBitRate = mediaInfo.Get(StreamKind.Audio, 0, "BitRate").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
-
- int.TryParse(aBitRate, out audioBitRate);
- int.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "StreamCount"), out streamCount);
-
- string audioChannelsStr = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
- int.TryParse(audioChannelsStr, out audioChannels);
-
- string audioChannelsStrOrig = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)_Original").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
- int.TryParse(audioChannelsStrOrig, out audioChannelsOrig);
-
- var audioChannelPositionsText = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions");
- var audioChannelPositionsTextOrig = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions_Original");
- var audioChannelPositions = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions/String2");
-
- string audioLanguages = mediaInfo.Get(StreamKind.General, 0, "Audio_Language_List");
-
- string videoProfile = mediaInfo.Get(StreamKind.Video, 0, "Format_Profile").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
- string audioProfile = mediaInfo.Get(StreamKind.Audio, 0, "Format_Profile").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
-
- var mediaInfoModel = new MediaInfoModel
- {
- ContainerFormat = mediaInfo.Get(StreamKind.General, 0, "Format"),
- VideoFormat = mediaInfo.Get(StreamKind.Video, 0, "Format"),
- VideoCodecID = mediaInfo.Get(StreamKind.Video, 0, "CodecID"),
- VideoProfile = videoProfile,
- VideoCodecLibrary = mediaInfo.Get(StreamKind.Video, 0, "Encoded_Library"),
- VideoBitrate = videoBitRate,
- VideoBitDepth = videoBitDepth,
- VideoMultiViewCount = videoMultiViewCount,
- VideoColourPrimaries = mediaInfo.Get(StreamKind.Video, 0, "colour_primaries"),
- VideoTransferCharacteristics = mediaInfo.Get(StreamKind.Video, 0, "transfer_characteristics"),
- VideoHdrFormat = mediaInfo.Get(StreamKind.Video, 0, "HDR_Format"),
- VideoHdrFormatCompatibility = mediaInfo.Get(StreamKind.Video, 0, "HDR_Format_Compatibility"),
- Height = height,
- Width = width,
- AudioFormat = mediaInfo.Get(StreamKind.Audio, 0, "Format"),
- AudioCodecID = mediaInfo.Get(StreamKind.Audio, 0, "CodecID"),
- AudioProfile = audioProfile,
- AudioCodecLibrary = mediaInfo.Get(StreamKind.Audio, 0, "Encoded_Library"),
- AudioAdditionalFeatures = mediaInfo.Get(StreamKind.Audio, 0, "Format_AdditionalFeatures"),
- AudioBitrate = audioBitRate,
- RunTime = GetBestRuntime(audioRuntime, videoRuntime, generalRuntime),
- AudioStreamCount = streamCount,
- AudioChannelsContainer = audioChannels,
- AudioChannelsStream = audioChannelsOrig,
- AudioChannelPositions = audioChannelPositions,
- AudioChannelPositionsTextContainer = audioChannelPositionsText,
- AudioChannelPositionsTextStream = audioChannelPositionsTextOrig,
- VideoFps = videoFrameRate,
- AudioLanguages = audioLanguages,
- Subtitles = subtitles,
- ScanType = scanType,
- SchemaRevision = CURRENT_MEDIA_INFO_SCHEMA_REVISION
- };
-
- return mediaInfoModel;
- }
- else
- {
- _logger.Warn("Unable to open media info from file: " + filename);
- }
- }
- catch (DllNotFoundException ex)
- {
- _logger.Error(ex, "mediainfo is required but was not found");
+ return mediaInfoModel;
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to parse media info from file: {0}", filename);
}
- finally
- {
- mediaInfo?.Close();
- }
return null;
}
@@ -220,19 +137,70 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
return info?.RunTime;
}
- private TimeSpan GetBestRuntime(int audio, int video, int general)
+ private static TimeSpan GetBestRuntime(TimeSpan? audio, TimeSpan? video, TimeSpan general)
{
- if (video == 0)
+ if (!video.HasValue || video.Value.TotalMilliseconds == 0)
{
- if (audio == 0)
+ if (!audio.HasValue || audio.Value.TotalMilliseconds == 0)
{
- return TimeSpan.FromMilliseconds(general);
+ return general;
}
- return TimeSpan.FromMilliseconds(audio);
+ return audio.Value;
}
- return TimeSpan.FromMilliseconds(video);
+ return video.Value;
+ }
+
+ private FFProbePixelFormat GetPixelFormat(string format)
+ {
+ return _pixelFormats.Find(x => x.Name == format);
+ }
+
+ public static HdrFormat GetHdrFormat(int bitDepth, string colorPrimaries, string transferFunction, List sideData)
+ {
+ if (bitDepth < 10)
+ {
+ return HdrFormat.None;
+ }
+
+ if (TryFindSideData(sideData, nameof(DoviConfigurationRecordSideData)))
+ {
+ return HdrFormat.DolbyVision;
+ }
+
+ if (!ValidHdrColourPrimaries.Contains(colorPrimaries) || !ValidHdrTransferFunctions.Contains(transferFunction))
+ {
+ return HdrFormat.None;
+ }
+
+ if (HlgTransferFunctions.Contains(transferFunction))
+ {
+ return HdrFormat.Hlg10;
+ }
+
+ if (PqTransferFunctions.Contains(transferFunction))
+ {
+ if (TryFindSideData(sideData, nameof(HdrDynamicMetadataSpmte2094)))
+ {
+ return HdrFormat.Hdr10Plus;
+ }
+
+ if (TryFindSideData(sideData, nameof(MasteringDisplayMetadata)) ||
+ TryFindSideData(sideData, nameof(ContentLightLevelMetadata)))
+ {
+ return HdrFormat.Hdr10;
+ }
+
+ return HdrFormat.Pq10;
+ }
+
+ return HdrFormat.None;
+ }
+
+ private static bool TryFindSideData(List list, string typeName)
+ {
+ return list?.Find(x => x.GetType().Name == typeName) != null;
}
}
}
diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromMediaInfo.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromMediaInfo.cs
index a97619a3e..31e9ef0bf 100644
--- a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromMediaInfo.cs
+++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Language/AugmentLanguageFromMediaInfo.cs
@@ -19,13 +19,13 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenter
return null;
}
- var audioLanguages = localMovie.MediaInfo.AudioLanguages.Split('/').Select(l => l.Trim()).Distinct().ToList();
+ var audioLanguages = localMovie.MediaInfo.AudioLanguages.Distinct().ToList();
var languages = new List();
foreach (var audioLanguage in audioLanguages)
{
- var language = IsoLanguages.FindByName(audioLanguage)?.Language;
+ var language = IsoLanguages.Find(audioLanguage)?.Language;
languages.AddIfNotNull(language);
}
diff --git a/src/NzbDrone.Core/Notifications/Discord/Discord.cs b/src/NzbDrone.Core/Notifications/Discord/Discord.cs
index 083d07c54..336aefb7d 100644
--- a/src/NzbDrone.Core/Notifications/Discord/Discord.cs
+++ b/src/NzbDrone.Core/Notifications/Discord/Discord.cs
@@ -187,11 +187,11 @@ namespace NzbDrone.Core.Notifications.Discord
break;
case DiscordImportFieldType.Languages:
discordField.Name = "Languages";
- discordField.Value = message.MovieFile.MediaInfo.AudioLanguages;
+ discordField.Value = message.MovieFile.MediaInfo.AudioLanguages.ConcatToString("/");
break;
case DiscordImportFieldType.Subtitles:
discordField.Name = "Subtitles";
- discordField.Value = message.MovieFile.MediaInfo.Subtitles;
+ discordField.Value = message.MovieFile.MediaInfo.Subtitles.ConcatToString("/");
break;
case DiscordImportFieldType.Release:
discordField.Name = "Release";
diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs
index 3224dc013..48d649be8 100644
--- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs
+++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -69,6 +70,31 @@ namespace NzbDrone.Core.Organizer
private static readonly Regex ReservedDeviceNamesRegex = new Regex(@"^(?:aux|com[1-9]|con|lpt[1-9]|nul|prn)\.", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ // generated from https://www.loc.gov/standards/iso639-2/ISO-639-2_utf-8.txt
+ public static readonly ImmutableDictionary Iso639BTMap = new Dictionary
+ {
+ { "alb", "sqi" },
+ { "arm", "hye" },
+ { "baq", "eus" },
+ { "bur", "mya" },
+ { "chi", "zho" },
+ { "cze", "ces" },
+ { "dut", "nld" },
+ { "fre", "fra" },
+ { "geo", "kat" },
+ { "ger", "deu" },
+ { "gre", "ell" },
+ { "ice", "isl" },
+ { "mac", "mkd" },
+ { "mao", "mri" },
+ { "may", "msa" },
+ { "per", "fas" },
+ { "rum", "ron" },
+ { "slo", "slk" },
+ { "tib", "bod" },
+ { "wel", "cym" }
+ }.ToImmutableDictionary();
+
public FileNameBuilder(INamingConfigService namingConfigService,
IQualityDefinitionService qualityDefinitionService,
IUpdateMediaInfo mediaInfoUpdater,
@@ -344,8 +370,8 @@ namespace NzbDrone.Core.Organizer
var videoCodec = MediaInfoFormatter.FormatVideoCodec(movieFile.MediaInfo, sceneName);
var audioCodec = MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, sceneName);
var audioChannels = MediaInfoFormatter.FormatAudioChannels(movieFile.MediaInfo);
- var audioLanguages = movieFile.MediaInfo.AudioLanguages ?? string.Empty;
- var subtitles = movieFile.MediaInfo.Subtitles ?? string.Empty;
+ var audioLanguages = movieFile.MediaInfo.AudioLanguages ?? new List();
+ var subtitles = movieFile.MediaInfo.Subtitles ?? new List();
var mediaInfoAudioLanguages = GetLanguagesToken(audioLanguages);
if (!mediaInfoAudioLanguages.IsNullOrWhiteSpace())
@@ -365,7 +391,7 @@ namespace NzbDrone.Core.Organizer
mediaInfoSubtitleLanguages = $"[{mediaInfoSubtitleLanguages}]";
}
- var videoBitDepth = movieFile.MediaInfo.VideoBitDepth > 0 ? movieFile.MediaInfo.VideoBitDepth.ToString() : string.Empty;
+ var videoBitDepth = movieFile.MediaInfo.VideoBitDepth > 0 ? movieFile.MediaInfo.VideoBitDepth.ToString() : 8.ToString();
var audioChannelsFormatted = audioChannels > 0 ?
audioChannels.ToString("F1", CultureInfo.InvariantCulture) :
string.Empty;
@@ -405,42 +431,29 @@ namespace NzbDrone.Core.Organizer
tokenHandlers["{Custom Formats}"] = m => string.Join(" ", customFormats.Where(x => x.IncludeCustomFormatWhenRenaming));
}
- private string GetLanguagesToken(string mediaInfoLanguages)
+ private string GetLanguagesToken(List mediaInfoLanguages)
{
- List tokens = new List();
- foreach (var item in mediaInfoLanguages.Split('/'))
+ var tokens = new List();
+ foreach (var item in mediaInfoLanguages)
{
- if (!string.IsNullOrWhiteSpace(item))
+ if (!string.IsNullOrWhiteSpace(item) && item != "und")
{
tokens.Add(item.Trim());
}
}
- var cultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures);
for (int i = 0; i < tokens.Count; i++)
{
- if (tokens[i] == "Swedis")
- {
- // Probably typo in mediainfo (should be 'Swedish')
- tokens[i] = "SV";
- continue;
- }
-
- if (tokens[i] == "Chinese" && OsInfo.IsNotWindows)
- {
- // Mono only has 'Chinese (Simplified)' & 'Chinese (Traditional)'
- tokens[i] = "ZH";
- continue;
- }
-
try
{
- var cultureInfo = cultures.FirstOrDefault(p => p.EnglishName.RemoveAccent() == tokens[i]);
-
- if (cultureInfo != null)
+ var token = tokens[i].ToLowerInvariant();
+ if (Iso639BTMap.TryGetValue(token, out var mapped))
{
- tokens[i] = cultureInfo.TwoLetterISOLanguageName.ToUpper();
+ token = mapped;
}
+
+ var cultureInfo = new CultureInfo(token);
+ tokens[i] = cultureInfo.TwoLetterISOLanguageName.ToUpper();
}
catch
{
diff --git a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs
index 140ab8673..1c2d942e0 100644
--- a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs
+++ b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs
@@ -30,13 +30,13 @@ namespace NzbDrone.Core.Organizer
VideoFormat = "AVC",
VideoBitDepth = 10,
VideoMultiViewCount = 2,
- VideoColourPrimaries = "BT.2020",
+ VideoColourPrimaries = "bt2020",
VideoTransferCharacteristics = "HLG",
AudioFormat = "DTS",
- AudioChannelsContainer = 6,
- AudioChannelPositions = "3/2/0.1",
- AudioLanguages = "German",
- Subtitles = "English/German"
+ AudioChannels = 6,
+ AudioChannelPositions = "5.1",
+ AudioLanguages = new List { "ger" },
+ Subtitles = new List { "eng", "ger" }
};
_movieFile = new MovieFile
diff --git a/src/NzbDrone.Core/Parser/IsoLanguages.cs b/src/NzbDrone.Core/Parser/IsoLanguages.cs
index b0493dd7b..f12c024ae 100644
--- a/src/NzbDrone.Core/Parser/IsoLanguages.cs
+++ b/src/NzbDrone.Core/Parser/IsoLanguages.cs
@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.InteropServices.ComTypes;
using NzbDrone.Core.Languages;
+using NzbDrone.Core.Organizer;
namespace NzbDrone.Core.Parser
{
@@ -62,6 +64,11 @@ namespace NzbDrone.Core.Parser
else if (langCode.Length == 3)
{
//Lookup ISO639-2T code
+ if (FileNameBuilder.Iso639BTMap.TryGetValue(langCode, out var mapped))
+ {
+ langCode = mapped;
+ }
+
return All.FirstOrDefault(l => l.ThreeLetterCode == langCode);
}
diff --git a/src/NzbDrone.Core/Radarr.Core.csproj b/src/NzbDrone.Core/Radarr.Core.csproj
index 4e6ae8321..e5dbfcf1a 100644
--- a/src/NzbDrone.Core/Radarr.Core.csproj
+++ b/src/NzbDrone.Core/Radarr.Core.csproj
@@ -5,6 +5,7 @@
+
@@ -19,6 +20,7 @@
+
diff --git a/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs b/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs
index 950cfd961..1a1abd2ed 100644
--- a/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs
+++ b/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs
@@ -128,10 +128,11 @@ namespace NzbDrone.Update.UpdateEngine
_logger.Info("Copying new files to target folder");
_diskTransferService.MirrorFolder(_appFolderInfo.GetUpdatePackageFolder(), installationFolder);
- // Set executable flag on app
+ // Set executable flag on app and ffprobe
if (OsInfo.IsOsx || (OsInfo.IsLinux && PlatformInfo.IsNetCore))
{
_diskProvider.SetFilePermissions(Path.Combine(installationFolder, "Radarr"), "755", null);
+ _diskProvider.SetFilePermissions(Path.Combine(installationFolder, "ffprobe"), "755", null);
}
}
catch (Exception e)
diff --git a/src/Radarr.Api.V3/MovieFiles/MediaInfoResource.cs b/src/Radarr.Api.V3/MovieFiles/MediaInfoResource.cs
index 897b90574..0dc5f6272 100644
--- a/src/Radarr.Api.V3/MovieFiles/MediaInfoResource.cs
+++ b/src/Radarr.Api.V3/MovieFiles/MediaInfoResource.cs
@@ -1,4 +1,5 @@
using System;
+using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaFiles.MediaInfo;
using Radarr.Http.REST;
@@ -6,7 +7,6 @@ namespace Radarr.Api.V3.MovieFiles
{
public class MediaInfoResource : RestResource
{
- public string AudioAdditionalFeatures { get; set; }
public int AudioBitrate { get; set; }
public decimal AudioChannels { get; set; }
public string AudioCodec { get; set; }
@@ -33,20 +33,19 @@ namespace Radarr.Api.V3.MovieFiles
return new MediaInfoResource
{
- AudioAdditionalFeatures = model.AudioAdditionalFeatures,
AudioBitrate = model.AudioBitrate,
AudioChannels = MediaInfoFormatter.FormatAudioChannels(model),
- AudioLanguages = model.AudioLanguages,
+ AudioLanguages = model.AudioLanguages.ConcatToString("/"),
AudioStreamCount = model.AudioStreamCount,
AudioCodec = MediaInfoFormatter.FormatAudioCodec(model, sceneName),
VideoBitDepth = model.VideoBitDepth,
VideoBitrate = model.VideoBitrate,
VideoCodec = MediaInfoFormatter.FormatVideoCodec(model, sceneName),
- VideoFps = model.VideoFps,
+ VideoFps = Math.Round(model.VideoFps, 3),
Resolution = $"{model.Width}x{model.Height}",
RunTime = FormatRuntime(model.RunTime),
ScanType = model.ScanType,
- Subtitles = model.Subtitles
+ Subtitles = model.Subtitles.ConcatToString("/")
};
}
diff --git a/src/Runtimes/linux-x64/Radarr.Core.dll.config b/src/Runtimes/linux-x64/Radarr.Core.dll.config
deleted file mode 100644
index f40a271ce..000000000
--- a/src/Runtimes/linux-x64/Radarr.Core.dll.config
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/Runtimes/osx-x64/libmediainfo.dylib b/src/Runtimes/osx-x64/libmediainfo.dylib
deleted file mode 100644
index c9a4fa29a..000000000
Binary files a/src/Runtimes/osx-x64/libmediainfo.dylib and /dev/null differ
diff --git a/src/Runtimes/win-x64/MediaInfo.dll b/src/Runtimes/win-x64/MediaInfo.dll
deleted file mode 100644
index a88193891..000000000
Binary files a/src/Runtimes/win-x64/MediaInfo.dll and /dev/null differ
diff --git a/src/Runtimes/win-x86/MediaInfo.dll b/src/Runtimes/win-x86/MediaInfo.dll
deleted file mode 100644
index 7c556d260..000000000
Binary files a/src/Runtimes/win-x86/MediaInfo.dll and /dev/null differ