New: MediaInfo -> FFProbe
Co-Authored-By: ta264 <ta264@users.noreply.github.com>
This commit is contained in:
parent
a4232549cb
commit
fa0fc3158b
|
@ -7,5 +7,6 @@
|
||||||
<add key="Mono.Posix.NETStandard" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/Mono.Posix.NETStandard/nuget/v3/index.json" />
|
<add key="Mono.Posix.NETStandard" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/Mono.Posix.NETStandard/nuget/v3/index.json" />
|
||||||
<add key="SQLite" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/SQLite/nuget/v3/index.json" />
|
<add key="SQLite" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/SQLite/nuget/v3/index.json" />
|
||||||
<add key="coverlet-nightly" value="https://pkgs.dev.azure.com/Servarr/coverlet/_packaging/coverlet-nightly/nuget/v3/index.json" />
|
<add key="coverlet-nightly" value="https://pkgs.dev.azure.com/Servarr/coverlet/_packaging/coverlet-nightly/nuget/v3/index.json" />
|
||||||
|
<add key="FFMpegCore" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/FFMpegCore/nuget/v3/index.json" />
|
||||||
</packageSources>
|
</packageSources>
|
||||||
</configuration>
|
</configuration>
|
|
@ -75,10 +75,6 @@ namespace NzbDrone.Common.Composition
|
||||||
{
|
{
|
||||||
mappedName = "libsqlite3.so.0";
|
mappedName = "libsqlite3.so.0";
|
||||||
}
|
}
|
||||||
else if (libraryName == "mediainfo")
|
|
||||||
{
|
|
||||||
mappedName = "libmediainfo.so.0";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NativeLibrary.Load(mappedName, assembly, dllImportSearchPath);
|
return NativeLibrary.Load(mappedName, assembly, dllImportSearchPath);
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,45 +10,30 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
|
||||||
{
|
{
|
||||||
private static string sceneName = "My.Series.S01E01-Sonarr";
|
private static string sceneName = "My.Series.S01E01-Sonarr";
|
||||||
|
|
||||||
[TestCase("AC-3", "AC3")]
|
[TestCase("mp2, , ", "droned.s01e03.swedish.720p.hdtv.x264-prince", "MP2")]
|
||||||
[TestCase("E-AC-3", "EAC3")]
|
[TestCase("vorbis, , ", "DB Super HDTV", "Vorbis")]
|
||||||
[TestCase("MPEG Audio", "MPEG Audio")]
|
[TestCase("pcm_s16le, , ", "DW DVDRip XviD-idTV, ", "PCM")]
|
||||||
[TestCase("DTS", "DTS")]
|
[TestCase("truehd, , ", "", "TrueHD")]
|
||||||
public void should_format_audio_format_legacy(string audioFormat, string expectedFormat)
|
[TestCase("truehd, , ", "TrueHD", "TrueHD")]
|
||||||
{
|
[TestCase("truehd, thd+, ", "Atmos", "TrueHD Atmos")]
|
||||||
var mediaInfoModel = new MediaInfoModel
|
[TestCase("truehd, thd+, ", "TrueHD.Atmos.7.1", "TrueHD Atmos")]
|
||||||
{
|
[TestCase("truehd, thd+, ", "", "TrueHD Atmos")]
|
||||||
AudioFormat = audioFormat
|
[TestCase("wmav1, , ", "Droned.wmv", "WMA")]
|
||||||
};
|
[TestCase("wmav2, , ", "B.N.S04E18.720p.WEB-DL", "WMA")]
|
||||||
|
[TestCase("opus, , ", "Roadkill Ep3x11 - YouTube.webm", "Opus")]
|
||||||
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
|
[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("MPEG Audio, A_MPEG/L2, , , ", "droned.s01e03.swedish.720p.hdtv.x264-prince", "MP2")]
|
[TestCase("dts, , DTS-HD MA", "DTS-HD.MA", "DTS-HD MA")]
|
||||||
[TestCase("Vorbis, A_VORBIS, , Xiph.Org libVorbis I 20101101 (Schaufenugget), ", "DB Super HDTV", "Vorbis")]
|
[TestCase("dts, , DTS-ES", "DTS-ES", "DTS-ES")]
|
||||||
[TestCase("PCM, 1, , , ", "DW DVDRip XviD-idTV, ", "PCM")] // Dubbed most likely
|
[TestCase("dts, , DTS-ES", "DTS", "DTS-ES")]
|
||||||
[TestCase("TrueHD, A_TRUEHD, , , ", "", "TrueHD")]
|
[TestCase("dts, , DTS-HD HRA", "DTSHD-HRA", "DTS-HD HRA")]
|
||||||
[TestCase("MLP FBA, A_TRUEHD, , , ", "TrueHD", "TrueHD")]
|
[TestCase("dts, , DTS", "DTS", "DTS")]
|
||||||
[TestCase("MLP FBA, A_TRUEHD, , , 16-ch", "Atmos", "TrueHD Atmos")]
|
[TestCase("eac3, ec+3, ", "EAC3.Atmos", "EAC3 Atmos")]
|
||||||
[TestCase("Atmos / TrueHD, A_TRUEHD, , , ", "TrueHD.Atmos.7.1", "TrueHD Atmos")]
|
[TestCase("eac3, , ", "DDP5.1", "EAC3")]
|
||||||
[TestCase("Atmos / TrueHD / AC-3, 131, , , ", "", "TrueHD Atmos")]
|
[TestCase("ac3, , ", "DD5.1", "AC3")]
|
||||||
[TestCase("WMA, 161, , , ", "Droned.wmv", "WMA")]
|
[TestCase("adpcm_ima_qt, , ", "Custom?", "PCM")]
|
||||||
[TestCase("WMA, 162, Pro, , ", "B.N.S04E18.720p.WEB-DL", "WMA")]
|
[TestCase("adpcm_ms, , ", "Custom", "PCM")]
|
||||||
[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", "Carbon.Copy.S02E01.The.Phreak.Lady.1080p.NF.Webrip.x265.10bit.EAC3.5.1.Atmos-RGP", "EAC3 Atmos")]
|
|
||||||
[TestCase("E-AC-3, A_EAC3, , , ", "DD5.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)
|
public void should_format_audio_format(string audioFormatPack, string sceneName, string expectedFormat)
|
||||||
{
|
{
|
||||||
var split = audioFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
|
var split = audioFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
|
||||||
|
@ -56,26 +41,12 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
|
||||||
{
|
{
|
||||||
AudioFormat = split[0],
|
AudioFormat = split[0],
|
||||||
AudioCodecID = split[1],
|
AudioCodecID = split[1],
|
||||||
AudioProfile = split[2],
|
AudioProfile = split[2]
|
||||||
AudioCodecLibrary = split[3],
|
|
||||||
AudioAdditionalFeatures = split[4]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
|
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]
|
[Test]
|
||||||
public void should_return_AudioFormat_by_default()
|
public void should_return_AudioFormat_by_default()
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
|
@ -8,94 +8,60 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class FormatVideoCodecFixture : TestBase
|
public class FormatVideoCodecFixture : TestBase
|
||||||
{
|
{
|
||||||
[TestCase("AVC", null, "x264")]
|
[TestCase("mpeg2video, ", "Droned.S01E02.1080i.HDTV.DD5.1.MPEG2-NTb", "MPEG2")]
|
||||||
[TestCase("AVC", "source.title.x264.720p-Sonarr", "x264")]
|
[TestCase("mpeg2video, ", "", "MPEG2")]
|
||||||
[TestCase("AVC", "source.title.h264.720p-Sonarr", "h264")]
|
[TestCase("mpeg1video, ", "The.Simpsons.S13E04.INTERNAL-ANiVCD.mpg", "MPEG")]
|
||||||
[TestCase("V_MPEGH/ISO/HEVC", null, "x265")]
|
[TestCase("vc1, WVC1", "B.N.S04E18.720p.WEB-DL", "VC1")]
|
||||||
[TestCase("V_MPEGH/ISO/HEVC", "source.title.x265.720p-Sonarr", "x265")]
|
[TestCase("vc1, V_MS/VFW/FOURCC/WVC1", "", "VC1")]
|
||||||
[TestCase("V_MPEGH/ISO/HEVC", "source.title.h265.720p-Sonarr", "h265")]
|
[TestCase("vc1, WMV3", "It's Always Sunny S07E13 The Gang's RevengeHDTV.XviD-2HD.avi", "VC1")]
|
||||||
[TestCase("MPEG-2 Video", null, "MPEG2")]
|
[TestCase("h264, V.MPEG4/ISO/AVC", "pd.2015.S03E08.720p.iP.WEBRip.AAC2.0.H264-BTW", "h264")]
|
||||||
public void should_format_video_codec_with_source_title_legacy(string videoCodec, string sceneName, string expectedFormat)
|
[TestCase("h264, V_MPEG4/ISO/AVC", "Resistance.2019.S01E03.1080p.RTE.WEB-DL.AAC2.0.x264-RTN", "x264")]
|
||||||
{
|
[TestCase("wmv1, WMV1", "Droned.wmv", "WMV")]
|
||||||
var mediaInfoModel = new MediaInfoModel
|
[TestCase("wmv2, WMV2", "Droned.wmv", "WMV")]
|
||||||
{
|
[TestCase("mpeg4, XVID", "", "XviD")]
|
||||||
VideoCodec = videoCodec
|
[TestCase("mpeg4, DIVX", "", "DivX")]
|
||||||
};
|
[TestCase("mpeg4, divx", "", "DivX")]
|
||||||
|
[TestCase("mpeg4, DIV3", "spsm.dvdrip.divx.avi'.", "DivX")]
|
||||||
MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
|
[TestCase("msmpeg4, DIV3", "Exit the Dragon, Enter the Tiger (1976) 360p MPEG Audio.avi", "DivX")]
|
||||||
}
|
[TestCase("msmpeg4v2, DIV3", "Exit the Dragon, Enter the Tiger (1976) 360p MPEG Audio.avi", "DivX")]
|
||||||
|
[TestCase("msmpeg4v3, DIV3", "Exit the Dragon, Enter the Tiger (1976) 360p MPEG Audio.avi", "DivX")]
|
||||||
[TestCase("MPEG Video, 2, Main@High, ", "Droned.S01E02.1080i.HDTV.DD5.1.MPEG2-NTb", "MPEG2")]
|
[TestCase("vp6, 4", "Top Gear - S12E01 - Lorries - SD TV.flv", "VP6")]
|
||||||
[TestCase("MPEG Video, V_MPEG2, Main@High, ", "", "MPEG2")]
|
[TestCase("vp7, VP70", "Sweet Seymour.avi", "VP7")]
|
||||||
[TestCase("MPEG Video, , , ", "The.Simpsons.S13E04.INTERNAL-ANiVCD.mpg", "MPEG")]
|
[TestCase("vp8, V_VP8", "Dick.mkv", "VP8")]
|
||||||
[TestCase("VC-1, WVC1, Advanced@L4, ", "B.N.S04E18.720p.WEB-DL", "VC1")]
|
[TestCase("vp9, V_VP9", "Roadkill Ep3x11 - YouTube.webm", "VP9")]
|
||||||
[TestCase("VC-1, V_MS/VFW/FOURCC / WVC1, Advanced@L3, ", "", "VC1")]
|
[TestCase("h264, x264", "Ghost Advent - S04E05 - Stanley Hotel SDTV.avi", "x264")]
|
||||||
[TestCase("VC-1, WMV3, MP@LL, ", "It's Always Sunny S07E13 The Gang's RevengeHDTV.XviD-2HD.avi", "VC1")]
|
[TestCase("hevc, V_MPEGH/ISO/HEVC", "The BBT S11E12 The Matrimonial Metric 1080p 10bit AMZN WEB-DL", "h265")]
|
||||||
[TestCase("V.MPEG4/ISO/AVC, V.MPEG4/ISO/AVC, , ", "pd.2015.S03E08.720p.iP.WEBRip.AAC2.0.H264-BTW", "h264")]
|
[TestCase("mpeg4, mp4v-20", "", "")]
|
||||||
[TestCase("AVC / AVC, V_MPEG4/ISO/AVC, High@L4, ", "Resistance.2019.S01E03.1080p.RTE.WEB-DL.AAC2.0.x264-RTN", "x264")]
|
[TestCase("mpeg4, XVID", "American.Chopper.S06E07.Mountain.Creek.Bike.DSR.XviD-KRS", "XviD")]
|
||||||
[TestCase("WMV1, WMV1, , ", "Droned.wmv", "WMV")]
|
[TestCase("rpza, V_QUICKTIME", "Custom", "")]
|
||||||
[TestCase("WMV2, WMV2, , ", "Droned.wmv", "WMV")]
|
[TestCase("mpeg4, FMP4", "", "")]
|
||||||
[TestCase("xvid, xvid, , ", "", "XviD")]
|
[TestCase("mpeg4, MP42", "", "")]
|
||||||
[TestCase("div3, div3, , ", "spsm.dvdrip.divx.avi'.", "DivX")]
|
[TestCase("mpeg4, mp43", "Bubble.Guppies.S01E13.480p.WEB-DL.H.264-BTN-Custom", "")]
|
||||||
[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", "")]
|
|
||||||
public void should_format_video_format(string videoFormatPack, string sceneName, string expectedFormat)
|
public void should_format_video_format(string videoFormatPack, string sceneName, string expectedFormat)
|
||||||
{
|
{
|
||||||
var split = videoFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
|
var split = videoFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
|
||||||
var mediaInfoModel = new MediaInfoModel
|
var mediaInfoModel = new MediaInfoModel
|
||||||
{
|
{
|
||||||
VideoFormat = split[0],
|
VideoFormat = split[0],
|
||||||
VideoCodecID = split[1],
|
VideoCodecID = split[1]
|
||||||
VideoProfile = split[2],
|
|
||||||
VideoCodecLibrary = split[3]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
|
MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("AVC, AVC, , x264", "Some.Video.S01E01.h264", "x264")] // Force mediainfo tag
|
[TestCase("h264, x264", "Some.Video.S01E01.h264", "x264")] // Force mediainfo tag
|
||||||
[TestCase("HEVC, HEVC, , x265", "Some.Video.S01E01.h265", "x265")] // Force mediainfo tag
|
[TestCase("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("h264, ", "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("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("h264, ", "Some.Video.S01E01", "h264")] // Default value
|
||||||
[TestCase("HEVC, HEVC, , ", "Some.Video.S01E01", "h265")] // Default value
|
[TestCase("hevc, ", "Some.Video.S01E01", "h265")] // Default value
|
||||||
public void should_format_video_format_fallbacks(string videoFormatPack, string sceneName, string expectedFormat)
|
public void should_format_video_format_fallbacks(string videoFormatPack, string sceneName, string expectedFormat)
|
||||||
{
|
{
|
||||||
var split = videoFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
|
var split = videoFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
|
||||||
var mediaInfoModel = new MediaInfoModel
|
var mediaInfoModel = new MediaInfoModel
|
||||||
{
|
{
|
||||||
VideoFormat = split[0],
|
VideoFormat = split[0],
|
||||||
VideoCodecID = split[1],
|
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]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
|
MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
|
@ -8,63 +8,21 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class FormatVideoDynamicRangeFixture : TestBase
|
public class FormatVideoDynamicRangeFixture : TestBase
|
||||||
{
|
{
|
||||||
[TestCase(8, "", "", "", "", "")]
|
[TestCase(HdrFormat.None, "")]
|
||||||
[TestCase(8, "BT.601 NTSC", "BT.709", "", "", "")]
|
[TestCase(HdrFormat.Hlg10, "HDR")]
|
||||||
[TestCase(10, "BT.2020", "PQ", "", "", "HDR")]
|
[TestCase(HdrFormat.Pq10, "HDR")]
|
||||||
[TestCase(8, "BT.2020", "PQ", "", "", "")]
|
[TestCase(HdrFormat.Hdr10, "HDR")]
|
||||||
[TestCase(10, "BT.601 NTSC", "PQ", "", "", "")]
|
[TestCase(HdrFormat.Hdr10Plus, "HDR")]
|
||||||
[TestCase(10, "BT.2020", "BT.709", "", "", "")]
|
[TestCase(HdrFormat.DolbyVision, "HDR")]
|
||||||
[TestCase(10, "BT.2020", "HLG", "", "", "HDR")]
|
public void should_format_video_dynamic_range(HdrFormat format, string expectedVideoDynamicRange)
|
||||||
[TestCase(10, "", "", "Dolby Vision", "", "HDR")]
|
|
||||||
[TestCase(10, "", "", "SMPTE ST 2086", "HDR10", "HDR")]
|
|
||||||
[TestCase(8, "", "", "Dolby Vision", "", "")]
|
|
||||||
[TestCase(8, "", "", "SMPTE ST 2086", "HDR10", "")]
|
|
||||||
[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)
|
|
||||||
{
|
{
|
||||||
var mediaInfo = new MediaInfoModel
|
var mediaInfo = new MediaInfoModel
|
||||||
{
|
{
|
||||||
VideoBitDepth = bitDepth,
|
VideoHdrFormat = format,
|
||||||
VideoColourPrimaries = colourPrimaries,
|
SchemaRevision = 8
|
||||||
VideoTransferCharacteristics = transferCharacteristics,
|
|
||||||
VideoHdrFormat = hdrFormat,
|
|
||||||
VideoHdrFormatCompatibility = hdrFormatCompatibility,
|
|
||||||
SchemaRevision = 7
|
|
||||||
};
|
};
|
||||||
|
|
||||||
MediaInfoFormatter.FormatVideoDynamicRange(mediaInfo).Should().Be(expectedVideoDynamicRange);
|
MediaInfoFormatter.FormatVideoDynamicRange(mediaInfo).Should().Be(expectedVideoDynamicRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(8, "", "", "", "")]
|
|
||||||
[TestCase(8, "BT.601 NTSC", "BT.709", "", "")]
|
|
||||||
[TestCase(8, "BT.2020", "PQ", "", "")]
|
|
||||||
[TestCase(8, "", "", "Dolby Vision", "")]
|
|
||||||
[TestCase(8, "", "", "SMPTE ST 2086", "")]
|
|
||||||
[TestCase(10, "BT.601 NTSC", "PQ", "", "")]
|
|
||||||
[TestCase(10, "BT.2020", "BT.709", "", "")]
|
|
||||||
[TestCase(10, "BT.2020", "PQ", "", "PQ")]
|
|
||||||
[TestCase(10, "BT.2020", "HLG", "", "HLG")]
|
|
||||||
[TestCase(10, "", "", "SMPTE ST 2086", "HDR10")]
|
|
||||||
[TestCase(10, "", "", "HDR10", "HDR10")]
|
|
||||||
[TestCase(10, "", "", "SMPTE ST 2094 App 4", "HDR10Plus")]
|
|
||||||
[TestCase(10, "", "", "Dolby Vision", "DV")]
|
|
||||||
[TestCase(10, "BT.2020", "PQ", "Dolby Vision / SMPTE ST 2086", "DV HDR10")]
|
|
||||||
[TestCase(10, "", "", "SL-HDR1", "HDR")]
|
|
||||||
[TestCase(10, "", "", "SL-HDR2", "HDR")]
|
|
||||||
[TestCase(10, "", "", "SL-HDR3", "HDR")]
|
|
||||||
[TestCase(10, "", "", "Technicolor Advanced HDR", "HDR")]
|
|
||||||
public void should_format_video_dynamic_range_type(int bitDepth, string colourPrimaries, string transferCharacteristics, string hdrFormat, string expectedVideoDynamicRange)
|
|
||||||
{
|
|
||||||
var mediaInfo = new MediaInfoModel
|
|
||||||
{
|
|
||||||
VideoBitDepth = bitDepth,
|
|
||||||
VideoColourPrimaries = colourPrimaries,
|
|
||||||
VideoTransferCharacteristics = transferCharacteristics,
|
|
||||||
VideoHdrFormat = hdrFormat,
|
|
||||||
SchemaRevision = 7
|
|
||||||
};
|
|
||||||
|
|
||||||
MediaInfoFormatter.FormatVideoDynamicRangeType(mediaInfo).Should().Be(expectedVideoDynamicRange);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class FormatVideoDynamicRangeTypeFixture : TestBase
|
||||||
|
{
|
||||||
|
[TestCase(HdrFormat.None, "")]
|
||||||
|
[TestCase(HdrFormat.Hlg10, "HLG")]
|
||||||
|
[TestCase(HdrFormat.Pq10, "PQ")]
|
||||||
|
[TestCase(HdrFormat.Hdr10, "HDR10")]
|
||||||
|
[TestCase(HdrFormat.Hdr10Plus, "HDR10Plus")]
|
||||||
|
[TestCase(HdrFormat.DolbyVision, "DV")]
|
||||||
|
[TestCase(HdrFormat.DolbyVisionHdr10, "DV HDR10")]
|
||||||
|
[TestCase(HdrFormat.DolbyVisionHlg, "DV HLG")]
|
||||||
|
[TestCase(HdrFormat.DolbyVisionSdr, "DV SDR")]
|
||||||
|
public void should_format_video_dynamic_range_type(HdrFormat format, string expectedVideoDynamicRangeType)
|
||||||
|
{
|
||||||
|
var mediaInfo = new MediaInfoModel
|
||||||
|
{
|
||||||
|
VideoHdrFormat = format,
|
||||||
|
SchemaRevision = 9
|
||||||
|
};
|
||||||
|
|
||||||
|
MediaInfoFormatter.FormatVideoDynamicRangeType(mediaInfo).Should().Be(expectedVideoDynamicRangeType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,43 +0,0 @@
|
||||||
using FluentAssertions;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
|
||||||
using NzbDrone.Test.Common;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class MediaInfoModelExtensionsFixture : TestBase
|
|
||||||
{
|
|
||||||
[TestCase(8, "", "", "", HdrFormat.None)]
|
|
||||||
[TestCase(8, "BT.601 NTSC", "BT.709", "", HdrFormat.None)]
|
|
||||||
[TestCase(8, "BT.2020", "PQ", "", HdrFormat.None)]
|
|
||||||
[TestCase(8, "", "", "Dolby Vision", HdrFormat.None)]
|
|
||||||
[TestCase(8, "", "", "SMPTE ST 2086", HdrFormat.None)]
|
|
||||||
[TestCase(10, "BT.601 NTSC", "PQ", "", HdrFormat.None)]
|
|
||||||
[TestCase(10, "BT.2020", "BT.709", "", HdrFormat.None)]
|
|
||||||
[TestCase(10, "BT.2020", "PQ", "", HdrFormat.Pq10)]
|
|
||||||
[TestCase(10, "BT.2020", "HLG", "", HdrFormat.Hlg10)]
|
|
||||||
[TestCase(10, "", "", "SMPTE ST 2086", HdrFormat.Hdr10)]
|
|
||||||
[TestCase(10, "", "", "HDR10", HdrFormat.Hdr10)]
|
|
||||||
[TestCase(10, "", "", "SMPTE ST 2094 App 4", HdrFormat.Hdr10Plus)]
|
|
||||||
[TestCase(10, "", "", "Dolby Vision", HdrFormat.DolbyVision)]
|
|
||||||
[TestCase(10, "BT.2020", "PQ", "Dolby Vision / SMPTE ST 2086", HdrFormat.DolbyVisionHdr10)]
|
|
||||||
[TestCase(10, "", "", "SL-HDR1", HdrFormat.UnknownHdr)]
|
|
||||||
[TestCase(10, "", "", "SL-HDR2", HdrFormat.UnknownHdr)]
|
|
||||||
[TestCase(10, "", "", "SL-HDR3", HdrFormat.UnknownHdr)]
|
|
||||||
[TestCase(10, "", "", "Technicolor Advanced HDR", HdrFormat.UnknownHdr)]
|
|
||||||
public void should_get_hdr_format(int bitDepth, string colourPrimaries, string transferCharacteristics, string hdrFormat, HdrFormat expectedVideoDynamicRange)
|
|
||||||
{
|
|
||||||
var mediaInfo = new MediaInfoModel
|
|
||||||
{
|
|
||||||
VideoBitDepth = bitDepth,
|
|
||||||
VideoColourPrimaries = colourPrimaries,
|
|
||||||
VideoTransferCharacteristics = transferCharacteristics,
|
|
||||||
VideoHdrFormat = hdrFormat,
|
|
||||||
SchemaRevision = 7
|
|
||||||
};
|
|
||||||
|
|
||||||
MediaInfoModelExtensions.GetHdrFormat(mediaInfo).Should().Be(expectedVideoDynamicRange);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +1,13 @@
|
||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using FFMpegCore;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Test.Common.Categories;
|
using NzbDrone.Test.Common.Categories;
|
||||||
|
@ -40,33 +45,26 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
||||||
|
|
||||||
var info = Subject.GetMediaInfo(path);
|
var info = Subject.GetMediaInfo(path);
|
||||||
|
|
||||||
info.VideoCodec.Should().BeNull();
|
info.VideoFormat.Should().Be("h264");
|
||||||
info.VideoFormat.Should().Be("AVC");
|
|
||||||
info.VideoCodecID.Should().Be("avc1");
|
info.VideoCodecID.Should().Be("avc1");
|
||||||
info.VideoProfile.Should().Be("Baseline@L2.1");
|
info.VideoProfile.Should().Be("Constrained Baseline");
|
||||||
info.VideoCodecLibrary.Should().Be("");
|
info.AudioFormat.Should().Be("aac");
|
||||||
info.AudioFormat.Should().Be("AAC");
|
info.AudioCodecID.Should().Be("mp4a");
|
||||||
info.AudioCodecID.Should().BeOneOf("40", "mp4a-40-2");
|
info.AudioProfile.Should().Be("LC");
|
||||||
info.AudioProfile.Should().BeOneOf("", "LC");
|
info.AudioBitrate.Should().Be(125488);
|
||||||
info.AudioCodecLibrary.Should().Be("");
|
info.AudioChannels.Should().Be(2);
|
||||||
info.AudioBitrate.Should().Be(128000);
|
info.AudioChannelPositions.Should().Be("stereo");
|
||||||
info.AudioChannelsContainer.Should().Be(2);
|
info.AudioLanguages.Should().BeEquivalentTo("eng");
|
||||||
info.AudioChannelsStream.Should().Be(0);
|
|
||||||
info.AudioChannelPositionsTextContainer.Should().Be("Front: L R");
|
|
||||||
info.AudioChannelPositionsTextStream.Should().Be("");
|
|
||||||
info.AudioLanguages.Should().Be("English");
|
|
||||||
info.Height.Should().Be(320);
|
info.Height.Should().Be(320);
|
||||||
info.RunTime.Seconds.Should().Be(10);
|
info.RunTime.Seconds.Should().Be(10);
|
||||||
info.ScanType.Should().Be("Progressive");
|
info.ScanType.Should().Be("Progressive");
|
||||||
info.Subtitles.Should().Be("");
|
info.Subtitles.Should().BeEmpty();
|
||||||
info.VideoBitrate.Should().Be(193329);
|
info.VideoBitrate.Should().Be(193328);
|
||||||
info.VideoFps.Should().Be(24);
|
info.VideoFps.Should().Be(24);
|
||||||
info.Width.Should().Be(480);
|
info.Width.Should().Be(480);
|
||||||
info.VideoColourPrimaries.Should().Be("BT.601 NTSC");
|
info.VideoBitDepth.Should().Be(8);
|
||||||
info.VideoTransferCharacteristics.Should().Be("BT.709");
|
info.VideoColourPrimaries.Should().Be("smpte170m");
|
||||||
info.AudioAdditionalFeatures.Should().BeOneOf("", "LC");
|
info.VideoTransferCharacteristics.Should().Be("bt709");
|
||||||
info.VideoHdrFormat.Should().BeEmpty();
|
|
||||||
info.VideoHdrFormatCompatibility.Should().BeEmpty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -83,45 +81,62 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
||||||
|
|
||||||
var info = Subject.GetMediaInfo(path);
|
var info = Subject.GetMediaInfo(path);
|
||||||
|
|
||||||
info.VideoCodec.Should().BeNull();
|
info.VideoFormat.Should().Be("h264");
|
||||||
info.VideoFormat.Should().Be("AVC");
|
|
||||||
info.VideoCodecID.Should().Be("avc1");
|
info.VideoCodecID.Should().Be("avc1");
|
||||||
info.VideoProfile.Should().Be("Baseline@L2.1");
|
info.VideoProfile.Should().Be("Constrained Baseline");
|
||||||
info.VideoCodecLibrary.Should().Be("");
|
info.AudioFormat.Should().Be("aac");
|
||||||
info.AudioFormat.Should().Be("AAC");
|
info.AudioCodecID.Should().Be("mp4a");
|
||||||
info.AudioCodecID.Should().BeOneOf("40", "mp4a-40-2");
|
info.AudioProfile.Should().Be("LC");
|
||||||
info.AudioProfile.Should().BeOneOf("", "LC");
|
info.AudioBitrate.Should().Be(125488);
|
||||||
info.AudioCodecLibrary.Should().Be("");
|
info.AudioChannels.Should().Be(2);
|
||||||
info.AudioBitrate.Should().Be(128000);
|
info.AudioChannelPositions.Should().Be("stereo");
|
||||||
info.AudioChannelsContainer.Should().Be(2);
|
info.AudioLanguages.Should().BeEquivalentTo("eng");
|
||||||
info.AudioChannelsStream.Should().Be(0);
|
|
||||||
info.AudioChannelPositionsTextContainer.Should().Be("Front: L R");
|
|
||||||
info.AudioChannelPositionsTextStream.Should().Be("");
|
|
||||||
info.AudioLanguages.Should().Be("English");
|
|
||||||
info.Height.Should().Be(320);
|
info.Height.Should().Be(320);
|
||||||
info.RunTime.Seconds.Should().Be(10);
|
info.RunTime.Seconds.Should().Be(10);
|
||||||
info.ScanType.Should().Be("Progressive");
|
info.ScanType.Should().Be("Progressive");
|
||||||
info.Subtitles.Should().Be("");
|
info.Subtitles.Should().BeEmpty();
|
||||||
info.VideoBitrate.Should().Be(193329);
|
info.VideoBitrate.Should().Be(193328);
|
||||||
info.VideoFps.Should().Be(24);
|
info.VideoFps.Should().Be(24);
|
||||||
info.Width.Should().Be(480);
|
info.Width.Should().Be(480);
|
||||||
info.VideoColourPrimaries.Should().Be("BT.601 NTSC");
|
info.VideoColourPrimaries.Should().Be("smpte170m");
|
||||||
info.VideoTransferCharacteristics.Should().Be("BT.709");
|
info.VideoTransferCharacteristics.Should().Be("bt709");
|
||||||
info.AudioAdditionalFeatures.Should().BeOneOf("", "LC");
|
|
||||||
info.VideoHdrFormat.Should().BeEmpty();
|
|
||||||
info.VideoHdrFormatCompatibility.Should().BeEmpty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase(8, "", "", "", null, HdrFormat.None)]
|
||||||
public void should_dispose_file_after_scanning_mediainfo()
|
[TestCase(10, "", "", "", null, HdrFormat.None)]
|
||||||
|
[TestCase(10, "bt709", "bt709", "", null, HdrFormat.None)]
|
||||||
|
[TestCase(8, "bt2020", "smpte2084", "", null, HdrFormat.None)]
|
||||||
|
[TestCase(10, "bt2020", "bt2020-10", "", null, HdrFormat.Hlg10)]
|
||||||
|
[TestCase(10, "bt2020", "arib-std-b67", "", null, HdrFormat.Hlg10)]
|
||||||
|
[TestCase(10, "bt2020", "smpte2084", "", null, HdrFormat.Pq10)]
|
||||||
|
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.SideData", null, HdrFormat.Pq10)]
|
||||||
|
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.MasteringDisplayMetadata", null, HdrFormat.Hdr10)]
|
||||||
|
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.ContentLightLevelMetadata", null, HdrFormat.Hdr10)]
|
||||||
|
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.HdrDynamicMetadataSpmte2094", null, HdrFormat.Hdr10Plus)]
|
||||||
|
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", null, HdrFormat.DolbyVision)]
|
||||||
|
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", 1, HdrFormat.DolbyVisionHdr10)]
|
||||||
|
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", 2, HdrFormat.DolbyVisionSdr)]
|
||||||
|
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", 4, HdrFormat.DolbyVisionHlg)]
|
||||||
|
public void should_detect_hdr_correctly(int bitDepth, string colourPrimaries, string transferFunction, string sideDataTypes, int? doviConfigId, 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<SideData>().ToList();
|
||||||
|
|
||||||
var info = Subject.GetMediaInfo(path);
|
if (doviConfigId.HasValue)
|
||||||
|
{
|
||||||
|
sideData.ForEach(x =>
|
||||||
|
{
|
||||||
|
if (x.GetType().Name == "DoviConfigurationRecordSideData")
|
||||||
|
{
|
||||||
|
((DoviConfigurationRecordSideData)x).DvBlSignalCompatibilityId = doviConfigId.Value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var stream = new FileStream(path, FileMode.Open, FileAccess.Write);
|
var result = VideoFileInfoReader.GetHdrFormat(bitDepth, colourPrimaries, transferFunction, sideData);
|
||||||
|
|
||||||
stream.Close();
|
result.Should().Be(expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ using NzbDrone.Test.Common;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||||
{
|
{
|
||||||
|
[Platform(Exclude = "Win")]
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
|
|
||||||
public class FileNameBuilderFixture : CoreTest<FileNameBuilder>
|
public class FileNameBuilderFixture : CoreTest<FileNameBuilder>
|
||||||
|
@ -539,37 +540,44 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||||
|
|
||||||
_episodeFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
|
_episodeFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
|
||||||
{
|
{
|
||||||
VideoCodec = "AVC",
|
VideoFormat = "h264",
|
||||||
AudioFormat = "DTS",
|
AudioFormat = "dts",
|
||||||
AudioChannelsContainer = 6,
|
AudioLanguages = new List<string> { "eng", "spa" },
|
||||||
AudioLanguages = "English/Spanish",
|
Subtitles = new List<string> { "eng", "spa", "ita" }
|
||||||
Subtitles = "English/Spanish/Italian",
|
|
||||||
SchemaRevision = 3
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
|
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
|
||||||
.Should().Be("South.Park.S15E06.City.Sushi.X264.DTS[EN+ES].[EN+ES+IT]");
|
.Should().Be("South.Park.S15E06.City.Sushi.H264.DTS[EN+ES].[EN+ES+IT]");
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("Norwegian Bokmal", "NB")]
|
[TestCase("nob", "NB")]
|
||||||
[TestCase("Swedis", "SV")]
|
[TestCase("swe", "SV")]
|
||||||
[TestCase("Chinese", "ZH")]
|
[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)
|
public void should_format_languagecodes_properly(string language, string code)
|
||||||
{
|
{
|
||||||
_namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}.{MEDIAINFO.FULL}";
|
_namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}.{MEDIAINFO.FULL}";
|
||||||
|
|
||||||
_episodeFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
|
_episodeFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
|
||||||
{
|
{
|
||||||
VideoCodec = "AVC",
|
VideoFormat = "h264",
|
||||||
AudioFormat = "DTS",
|
AudioFormat = "dts",
|
||||||
AudioChannelsContainer = 6,
|
AudioChannels = 6,
|
||||||
AudioLanguages = "English",
|
AudioLanguages = new List<string> { "eng" },
|
||||||
Subtitles = language,
|
Subtitles = new List<string> { language },
|
||||||
SchemaRevision = 3
|
SchemaRevision = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
|
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
|
||||||
.Should().Be($"South.Park.S15E06.City.Sushi.X264.DTS.[{code}]");
|
.Should().Be($"South.Park.S15E06.City.Sushi.H264.DTS.[{code}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -579,18 +587,17 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||||
|
|
||||||
_episodeFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
|
_episodeFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
|
||||||
{
|
{
|
||||||
VideoCodec = "AVC",
|
VideoFormat = "h264",
|
||||||
AudioFormat = "DTS",
|
AudioFormat = "dts",
|
||||||
AudioChannelsContainer = 6,
|
AudioLanguages = new List<string> { "eng" },
|
||||||
AudioLanguages = "English",
|
Subtitles = new List<string> { "eng", "spa", "ita" }
|
||||||
Subtitles = "English/Spanish/Italian",
|
|
||||||
SchemaRevision = 3
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
|
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
|
||||||
.Should().Be("South.Park.S15E06.City.Sushi.X264.DTS.[EN+ES+IT]");
|
.Should().Be("South.Park.S15E06.City.Sushi.H264.DTS.[EN+ES+IT]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Ignore("not currently supported")]
|
||||||
[Test]
|
[Test]
|
||||||
public void should_remove_duplicate_non_word_characters()
|
public void should_remove_duplicate_non_word_characters()
|
||||||
{
|
{
|
||||||
|
@ -810,7 +817,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||||
|
|
||||||
_episodeFile.ReleaseGroup = null;
|
_episodeFile.ReleaseGroup = null;
|
||||||
|
|
||||||
GivenMediaInfoModel(audioLanguages: "English/German");
|
GivenMediaInfoModel(audioLanguages: "eng/deu");
|
||||||
|
|
||||||
_namingConfig.StandardEpisodeFormat = "{MediaInfo AudioLanguages}";
|
_namingConfig.StandardEpisodeFormat = "{MediaInfo AudioLanguages}";
|
||||||
|
|
||||||
|
@ -823,8 +830,8 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("English", "")]
|
[TestCase("eng", "")]
|
||||||
[TestCase("English/German", "[EN+DE]")]
|
[TestCase("eng/deu", "[EN+DE]")]
|
||||||
public void should_format_audio_languages(string audioLanguages, string expected)
|
public void should_format_audio_languages(string audioLanguages, string expected)
|
||||||
{
|
{
|
||||||
_episodeFile.ReleaseGroup = null;
|
_episodeFile.ReleaseGroup = null;
|
||||||
|
@ -837,8 +844,8 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||||
.Should().Be(expected);
|
.Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("English", "[EN]")]
|
[TestCase("eng", "[EN]")]
|
||||||
[TestCase("English/German", "[EN+DE]")]
|
[TestCase("eng/deu", "[EN+DE]")]
|
||||||
public void should_format_audio_languages_all(string audioLanguages, string expected)
|
public void should_format_audio_languages_all(string audioLanguages, string expected)
|
||||||
{
|
{
|
||||||
_episodeFile.ReleaseGroup = null;
|
_episodeFile.ReleaseGroup = null;
|
||||||
|
@ -851,15 +858,15 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||||
.Should().Be(expected);
|
.Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("English/German", "", "[EN+DE]")]
|
[TestCase("eng/deu", "", "[EN+DE]")]
|
||||||
[TestCase("English/Dutch/German", "", "[EN+NL+DE]")]
|
[TestCase("eng/nld/deu", "", "[EN+NL+DE]")]
|
||||||
[TestCase("English/German", ":DE", "[DE]")]
|
[TestCase("eng/deu", ":DE", "[DE]")]
|
||||||
[TestCase("English/Dutch/German", ":EN+NL", "[EN+NL]")]
|
[TestCase("eng/nld/deu", ":EN+NL", "[EN+NL]")]
|
||||||
[TestCase("English/Dutch/German", ":NL+EN", "[NL+EN]")]
|
[TestCase("eng/nld/deu", ":NL+EN", "[NL+EN]")]
|
||||||
[TestCase("English/Dutch/German", ":-NL", "[EN+DE]")]
|
[TestCase("eng/nld/deu", ":-NL", "[EN+DE]")]
|
||||||
[TestCase("English/Dutch/German", ":DE+", "[DE+-]")]
|
[TestCase("eng/nld/deu", ":DE+", "[DE+-]")]
|
||||||
[TestCase("English/Dutch/German", ":DE+NO.", "[DE].")]
|
[TestCase("eng/nld/deu", ":DE+NO.", "[DE].")]
|
||||||
[TestCase("English/Dutch/German", ":-EN-", "[NL+DE]-")]
|
[TestCase("eng/nld/deu", ":-EN-", "[NL+DE]-")]
|
||||||
public void should_format_subtitle_languages_all(string subtitleLanguages, string format, string expected)
|
public void should_format_subtitle_languages_all(string subtitleLanguages, string format, string expected)
|
||||||
{
|
{
|
||||||
_episodeFile.ReleaseGroup = null;
|
_episodeFile.ReleaseGroup = null;
|
||||||
|
@ -872,19 +879,15 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||||
.Should().Be(expected + "End");
|
.Should().Be(expected + "End");
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(8, "BT.601 NTSC", "BT.709", "South.Park.S15E06.City.Sushi")]
|
[TestCase(HdrFormat.None, "South.Park.S15E06.City.Sushi")]
|
||||||
[TestCase(10, "BT.2020", "PQ", "South.Park.S15E06.City.Sushi.HDR")]
|
[TestCase(HdrFormat.Hlg10, "South.Park.S15E06.City.Sushi.HDR")]
|
||||||
[TestCase(10, "BT.2020", "HLG", "South.Park.S15E06.City.Sushi.HDR")]
|
[TestCase(HdrFormat.Hdr10, "South.Park.S15E06.City.Sushi.HDR")]
|
||||||
[TestCase(0, null, null, "South.Park.S15E06.City.Sushi")]
|
public void should_include_hdr_for_mediainfo_videodynamicrange_with_valid_properties(HdrFormat hdrFormat, string expectedName)
|
||||||
public void should_include_hdr_for_mediainfo_videodynamicrange_with_valid_properties(int bitDepth,
|
|
||||||
string colourPrimaries,
|
|
||||||
string transferCharacteristics,
|
|
||||||
string expectedName)
|
|
||||||
{
|
{
|
||||||
_namingConfig.StandardEpisodeFormat =
|
_namingConfig.StandardEpisodeFormat =
|
||||||
"{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}.{MediaInfo VideoDynamicRange}";
|
"{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}.{MediaInfo VideoDynamicRange}";
|
||||||
|
|
||||||
GivenMediaInfoModel(videoBitDepth: bitDepth, videoColourPrimaries: colourPrimaries, videoTransferCharacteristics: transferCharacteristics);
|
GivenMediaInfoModel(hdrFormat: hdrFormat);
|
||||||
|
|
||||||
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
|
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
|
||||||
.Should().Be(expectedName);
|
.Should().Be(expectedName);
|
||||||
|
@ -968,14 +971,15 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||||
.Setup(u => u.Update(_episodeFile, _series))
|
.Setup(u => u.Update(_episodeFile, _series))
|
||||||
.Callback((EpisodeFile e, Series s) => e.MediaInfo = new MediaInfoModel
|
.Callback((EpisodeFile e, Series s) => e.MediaInfo = new MediaInfoModel
|
||||||
{
|
{
|
||||||
VideoCodec = "AVC",
|
VideoFormat = "AVC",
|
||||||
AudioFormat = "DTS",
|
AudioFormat = "DTS",
|
||||||
AudioChannelsContainer = 6,
|
AudioChannels = 6,
|
||||||
AudioLanguages = "English",
|
AudioLanguages = new List<string> { "eng" },
|
||||||
Subtitles = "English/Spanish/Italian",
|
Subtitles = new List<string> { "eng", "esp", "ita" },
|
||||||
VideoBitDepth = 10,
|
VideoBitDepth = 10,
|
||||||
VideoColourPrimaries = "BT.2020",
|
VideoColourPrimaries = "bt2020",
|
||||||
VideoTransferCharacteristics = "PQ",
|
VideoTransferCharacteristics = "PQ",
|
||||||
|
VideoHdrFormat = HdrFormat.Pq10,
|
||||||
SchemaRevision = 5
|
SchemaRevision = 5
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -984,26 +988,24 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||||
result.Should().EndWith("HDR");
|
result.Should().EndWith("HDR");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenMediaInfoModel(string videoCodec = "AVC",
|
private void GivenMediaInfoModel(string videoCodec = "h264",
|
||||||
string audioCodec = "DTS",
|
string audioCodec = "dts",
|
||||||
int audioChannels = 6,
|
int audioChannels = 6,
|
||||||
int videoBitDepth = 8,
|
int videoBitDepth = 8,
|
||||||
string videoColourPrimaries = "",
|
HdrFormat hdrFormat = HdrFormat.None,
|
||||||
string videoTransferCharacteristics = "",
|
string audioLanguages = "eng",
|
||||||
string audioLanguages = "English",
|
string subtitles = "eng/spa/ita",
|
||||||
string subtitles = "English/Spanish/Italian",
|
|
||||||
int schemaRevision = 5)
|
int schemaRevision = 5)
|
||||||
{
|
{
|
||||||
_episodeFile.MediaInfo = new MediaInfoModel
|
_episodeFile.MediaInfo = new MediaInfoModel
|
||||||
{
|
{
|
||||||
VideoCodec = videoCodec,
|
VideoFormat = videoCodec,
|
||||||
AudioFormat = audioCodec,
|
AudioFormat = audioCodec,
|
||||||
AudioChannelsContainer = audioChannels,
|
AudioChannels = audioChannels,
|
||||||
AudioLanguages = audioLanguages,
|
AudioLanguages = audioLanguages.Split("/").ToList(),
|
||||||
Subtitles = subtitles,
|
Subtitles = subtitles.Split("/").ToList(),
|
||||||
VideoBitDepth = videoBitDepth,
|
VideoBitDepth = videoBitDepth,
|
||||||
VideoColourPrimaries = videoColourPrimaries,
|
VideoHdrFormat = hdrFormat,
|
||||||
VideoTransferCharacteristics = videoTransferCharacteristics,
|
|
||||||
SchemaRevision = schemaRevision
|
SchemaRevision = schemaRevision
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(163)]
|
||||||
|
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<MediaInfoRaw>("SELECT Id, MediaInfo, SceneName FROM EpisodeFiles");
|
||||||
|
|
||||||
|
var updated = new List<MediaInfoRaw>();
|
||||||
|
|
||||||
|
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<MediaInfoBase>(row.MediaInfo, _serializerSettings);
|
||||||
|
if (mediaInfoVersion.SchemaRevision >= 8)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse and migrate
|
||||||
|
var mediaInfo = JsonSerializer.Deserialize<MediaInfo162>(row.MediaInfo, _serializerSettings);
|
||||||
|
|
||||||
|
var ffprobe = MigrateMediaInfo(mediaInfo, row.SceneName);
|
||||||
|
|
||||||
|
updated.Add(new MediaInfoRaw
|
||||||
|
{
|
||||||
|
Id = row.Id,
|
||||||
|
MediaInfo = JsonSerializer.Serialize(ffprobe, _serializerSettings)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateSql = "UPDATE EpisodeFiles SET MediaInfo = @MediaInfo WHERE Id = @Id";
|
||||||
|
conn.Execute(updateSql, updated, transaction: tran);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MediaInfo163 MigrateMediaInfo(MediaInfo162 old, string sceneName)
|
||||||
|
{
|
||||||
|
var m = new MediaInfo163
|
||||||
|
{
|
||||||
|
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(MediaInfo162 mediaInfo, MediaInfo163 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(MediaInfo162 mediaInfo, MediaInfo163 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(MediaInfo162 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(MediaInfo162 mediaInfo, MediaInfo163 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(MediaInfo162 mediaInfo, MediaInfo163 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(MediaInfo162 mediaInfo, MediaInfo163 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(MediaInfo162 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(MediaInfo162 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(MediaInfo162 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<string> MigrateLanguages(string mediaInfoLanguages)
|
||||||
|
{
|
||||||
|
var languages = new List<string>();
|
||||||
|
|
||||||
|
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.IsNotNullOrWhiteSpace() ? primary.Replace("BT.", "bt") : primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 MediaInfo162 : 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 MediaInfo163 : 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<string> AudioLanguages { get; set; }
|
||||||
|
public List<string> Subtitles { get; set; }
|
||||||
|
public string ScanType { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -319,14 +319,14 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
|
||||||
streamDetails.Add(video);
|
streamDetails.Add(video);
|
||||||
|
|
||||||
var audio = new XElement("audio");
|
var audio = new XElement("audio");
|
||||||
var audioChannelCount = episodeFile.MediaInfo.AudioChannelsStream > 0 ? episodeFile.MediaInfo.AudioChannelsStream : episodeFile.MediaInfo.AudioChannelsContainer;
|
var audioChannelCount = episodeFile.MediaInfo.AudioChannels;
|
||||||
audio.Add(new XElement("bitrate", episodeFile.MediaInfo.AudioBitrate));
|
audio.Add(new XElement("bitrate", episodeFile.MediaInfo.AudioBitrate));
|
||||||
audio.Add(new XElement("channels", audioChannelCount));
|
audio.Add(new XElement("channels", audioChannelCount));
|
||||||
audio.Add(new XElement("codec", MediaInfoFormatter.FormatAudioCodec(episodeFile.MediaInfo, sceneName)));
|
audio.Add(new XElement("codec", MediaInfoFormatter.FormatAudioCodec(episodeFile.MediaInfo, sceneName)));
|
||||||
audio.Add(new XElement("language", episodeFile.MediaInfo.AudioLanguages));
|
audio.Add(new XElement("language", episodeFile.MediaInfo.AudioLanguages));
|
||||||
streamDetails.Add(audio);
|
streamDetails.Add(audio);
|
||||||
|
|
||||||
if (episodeFile.MediaInfo.Subtitles.IsNotNullOrWhiteSpace())
|
if (episodeFile.MediaInfo.Subtitles != null && episodeFile.MediaInfo.Subtitles.Count > 0)
|
||||||
{
|
{
|
||||||
var subtitle = new XElement("subtitle");
|
var subtitle = new XElement("subtitle");
|
||||||
subtitle.Add(new XElement("language", episodeFile.MediaInfo.Subtitles));
|
subtitle.Add(new XElement("language", episodeFile.MediaInfo.Subtitles));
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.HealthCheck.Checks
|
|
||||||
{
|
|
||||||
public class MediaInfoDllCheck : HealthCheckBase
|
|
||||||
{
|
|
||||||
[MethodImpl(MethodImplOptions.NoOptimization)]
|
|
||||||
public override HealthCheck Check()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var mediaInfo = new MediaInfo();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, $"MediaInfo Library could not be loaded {e.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new HealthCheck(GetType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -49,7 +49,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||||
|
|
||||||
if (!runTime.HasValue)
|
if (!runTime.HasValue)
|
||||||
{
|
{
|
||||||
_logger.Error("Failed to get runtime from the file, make sure mediainfo is available");
|
_logger.Error("Failed to get runtime from the file, make sure ffprobe is available");
|
||||||
return DetectSampleResult.Indeterminate;
|
return DetectSampleResult.Indeterminate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles.MediaInfo
|
namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
{
|
{
|
||||||
public enum HdrFormat
|
public enum HdrFormat
|
||||||
|
@ -15,6 +9,8 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
Hdr10Plus,
|
Hdr10Plus,
|
||||||
Hlg10,
|
Hlg10,
|
||||||
DolbyVision,
|
DolbyVision,
|
||||||
DolbyVisionHdr10
|
DolbyVisionHdr10,
|
||||||
|
DolbyVisionSdr,
|
||||||
|
DolbyVisionHlg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
@ -12,9 +11,10 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
{
|
{
|
||||||
public static class MediaInfoFormatter
|
public static class MediaInfoFormatter
|
||||||
{
|
{
|
||||||
private const string ValidHdrColourPrimaries = "BT.2020";
|
|
||||||
private const string VideoDynamicRangeHdr = "HDR";
|
private const string VideoDynamicRangeHdr = "HDR";
|
||||||
|
|
||||||
|
private static readonly Regex PositionRegex = new Regex(@"(?<position>^\d\.\d)", RegexOptions.Compiled);
|
||||||
|
|
||||||
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(MediaInfoFormatter));
|
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(MediaInfoFormatter));
|
||||||
|
|
||||||
public static decimal FormatAudioChannels(MediaInfoModel mediaInfo)
|
public static decimal FormatAudioChannels(MediaInfoModel mediaInfo)
|
||||||
|
@ -23,101 +23,95 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
|
|
||||||
if (audioChannels == null || audioChannels == 0.0m)
|
if (audioChannels == null || audioChannels == 0.0m)
|
||||||
{
|
{
|
||||||
audioChannels = FormatAudioChannelsFromAudioChannelPositionsText(mediaInfo);
|
audioChannels = mediaInfo.AudioChannels;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioChannels == null || audioChannels == 0.0m)
|
return audioChannels.Value;
|
||||||
{
|
|
||||||
audioChannels = FormatAudioChannelsFromAudioChannels(mediaInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
return audioChannels ?? 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string FormatAudioCodec(MediaInfoModel mediaInfo, string sceneName)
|
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 audioCodecID = mediaInfo.AudioCodecID ?? string.Empty;
|
||||||
var audioProfile = mediaInfo.AudioProfile ?? 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())
|
if (audioFormat.Empty())
|
||||||
{
|
{
|
||||||
return string.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";
|
return "TrueHD Atmos";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioFormat.ContainsIgnoreCase("MLP FBA"))
|
if (audioFormat == "truehd")
|
||||||
{
|
|
||||||
if (splitAdditionalFeatures.ContainsIgnoreCase("16-ch"))
|
|
||||||
{
|
|
||||||
return "TrueHD Atmos";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "TrueHD";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audioFormat.ContainsIgnoreCase("TrueHD"))
|
|
||||||
{
|
{
|
||||||
return "TrueHD";
|
return "TrueHD";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioFormat.ContainsIgnoreCase("FLAC"))
|
if (audioFormat == "flac")
|
||||||
{
|
{
|
||||||
return "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";
|
return "DTS-HD MA";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (splitAdditionalFeatures.ContainsIgnoreCase("ES"))
|
if (audioProfile == "DTS-ES")
|
||||||
{
|
{
|
||||||
return "DTS-ES";
|
return "DTS-ES";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (splitAdditionalFeatures.ContainsIgnoreCase("XBR"))
|
if (audioProfile == "DTS-HD HRA")
|
||||||
{
|
{
|
||||||
return "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";
|
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";
|
return "EAC3";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioFormat.ContainsIgnoreCase("AC-3"))
|
if (audioFormat == "ac3")
|
||||||
{
|
{
|
||||||
return "AC3";
|
return "AC3";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioFormat.ContainsIgnoreCase("AAC"))
|
if (audioFormat == "aac")
|
||||||
{
|
{
|
||||||
if (audioCodecID == "A_AAC/MPEG4/LC/SBR")
|
if (audioCodecID == "A_AAC/MPEG4/LC/SBR")
|
||||||
{
|
{
|
||||||
|
@ -127,450 +121,169 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
return "AAC";
|
return "AAC";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioFormat.ContainsIgnoreCase("mp3"))
|
if (audioFormat == "mp3")
|
||||||
{
|
{
|
||||||
return "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 "MP2";
|
||||||
{
|
|
||||||
return "MP3";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaInfo.AudioCodecID == "A_MPEG/L2" || mediaInfo.AudioProfile == "Layer 2")
|
|
||||||
{
|
|
||||||
return "MP2";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioFormat.ContainsIgnoreCase("Opus"))
|
if (audioFormat == "opus")
|
||||||
{
|
{
|
||||||
return "Opus";
|
return "Opus";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioFormat.ContainsIgnoreCase("PCM"))
|
if (audioFormat.StartsWith("pcm_") || audioFormat.StartsWith("adpcm_"))
|
||||||
{
|
{
|
||||||
return "PCM";
|
return "PCM";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioFormat.ContainsIgnoreCase("ADPCM"))
|
if (audioFormat == "vorbis")
|
||||||
{
|
|
||||||
return "PCM";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audioFormat.ContainsIgnoreCase("Vorbis"))
|
|
||||||
{
|
{
|
||||||
return "Vorbis";
|
return "Vorbis";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioFormat.ContainsIgnoreCase("WMA"))
|
if (audioFormat == "wmav1" ||
|
||||||
|
audioFormat == "wmav2" ||
|
||||||
|
audioFormat == "wmapro")
|
||||||
{
|
{
|
||||||
return "WMA";
|
return "WMA";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioFormat.ContainsIgnoreCase("A_QUICKTIME"))
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Debug()
|
Logger.Debug()
|
||||||
.Message("Unknown audio format: '{0}' in '{1}'.", string.Join(", ", mediaInfo.AudioFormat, audioCodecID, audioProfile, audioCodecLibrary, mediaInfo.AudioAdditionalFeatures), sceneName)
|
.Message("Unknown audio format: '{0}' in '{1}'.", mediaInfo.RawStreamData, sceneName)
|
||||||
.WriteSentryWarn("UnknownAudioFormat", mediaInfo.ContainerFormat, mediaInfo.AudioFormat, audioCodecID)
|
.WriteSentryWarn("UnknownAudioFormatFFProbe", mediaInfo.ContainerFormat, mediaInfo.AudioFormat, audioCodecID)
|
||||||
.Write();
|
.Write();
|
||||||
|
|
||||||
return mediaInfo.AudioFormat;
|
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)
|
public static string FormatVideoCodec(MediaInfoModel mediaInfo, string sceneName)
|
||||||
{
|
{
|
||||||
if (mediaInfo.VideoFormat == null)
|
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 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())
|
if (videoFormat.Empty())
|
||||||
{
|
{
|
||||||
return result;
|
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";
|
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");
|
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");
|
return GetSceneNameMatch(sceneName, "HEVC", "x265", "h265");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoFormat.ContainsIgnoreCase("MPEG Video"))
|
if (videoFormat == "mpeg2video")
|
||||||
{
|
|
||||||
if (videoCodecID == "2" || videoCodecID == "V_MPEG2")
|
|
||||||
{
|
|
||||||
return "MPEG2";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (videoCodecID.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
return "MPEG";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (videoFormat.ContainsIgnoreCase("MPEG-2 Video"))
|
|
||||||
{
|
{
|
||||||
return "MPEG2";
|
return "MPEG2";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoFormat.ContainsIgnoreCase("MPEG-4 Visual"))
|
if (videoFormat == "mpeg1video")
|
||||||
{
|
{
|
||||||
if (videoCodecID.ContainsIgnoreCase("XVID") ||
|
return "MPEG";
|
||||||
videoCodecLibrary.StartsWithIgnoreCase("XviD"))
|
}
|
||||||
|
|
||||||
|
if (videoFormat == "mpeg4" || videoFormat.Contains("msmpeg4"))
|
||||||
|
{
|
||||||
|
if (videoCodecID == "XVID")
|
||||||
{
|
{
|
||||||
return "XviD";
|
return "XviD";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoCodecID.ContainsIgnoreCase("DIV3") ||
|
if (videoCodecID == "DIV3" ||
|
||||||
videoCodecID.ContainsIgnoreCase("DIVX") ||
|
videoCodecID == "DX50" ||
|
||||||
videoCodecID.ContainsIgnoreCase("DX50") ||
|
videoCodecID.ToUpperInvariant() == "DIVX")
|
||||||
videoCodecLibrary.StartsWithIgnoreCase("DivX"))
|
|
||||||
{
|
{
|
||||||
return "DivX";
|
return "DivX";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoFormat.ContainsIgnoreCase("MPEG-4 Visual") || videoFormat.ContainsIgnoreCase("mp4v"))
|
if (videoFormat == "vc1")
|
||||||
{
|
|
||||||
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 == "")
|
|
||||||
{
|
|
||||||
return ""; // Unknown mp4v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (videoFormat.ContainsIgnoreCase("VC-1"))
|
|
||||||
{
|
{
|
||||||
return "VC1";
|
return "VC1";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoFormat.ContainsIgnoreCase("VP6") || videoFormat.ContainsIgnoreCase("VP7") ||
|
if (videoFormat == "av1")
|
||||||
videoFormat.ContainsIgnoreCase("VP8") || videoFormat.ContainsIgnoreCase("VP9"))
|
|
||||||
{
|
{
|
||||||
return videoFormat.First().ToUpperInvariant();
|
return "AV1";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoFormat.ContainsIgnoreCase("WMV1") || videoFormat.ContainsIgnoreCase("WMV2"))
|
if (videoFormat == "vp6" ||
|
||||||
|
videoFormat == "vp7" ||
|
||||||
|
videoFormat == "vp8" ||
|
||||||
|
videoFormat == "vp9")
|
||||||
|
{
|
||||||
|
return videoFormat.ToUpperInvariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoFormat == "wmv1" ||
|
||||||
|
videoFormat == "wmv2" ||
|
||||||
|
videoFormat == "wmv3")
|
||||||
{
|
{
|
||||||
return "WMV";
|
return "WMV";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoFormat.ContainsIgnoreCase("DivX") || videoFormat.ContainsIgnoreCase("div3"))
|
if (videoFormat == "qtrle" ||
|
||||||
|
videoFormat == "rpza" ||
|
||||||
|
videoFormat == "rv10" ||
|
||||||
|
videoFormat == "rv20" ||
|
||||||
|
videoFormat == "rv30" ||
|
||||||
|
videoFormat == "rv40" ||
|
||||||
|
videoFormat == "cinepak")
|
||||||
{
|
{
|
||||||
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 "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Debug()
|
Logger.Debug()
|
||||||
.Message("Unknown video format: '{0}' in '{1}'.", string.Join(", ", videoFormat, videoCodecID, videoProfile, videoCodecLibrary), sceneName)
|
.Message("Unknown video format: '{0}' in '{1}'.", mediaInfo.RawStreamData, sceneName)
|
||||||
.WriteSentryWarn("UnknownVideoFormat", mediaInfo.ContainerFormat, mediaInfo.VideoFormat, videoCodecID)
|
.WriteSentryWarn("UnknownVideoFormatFFProbe", mediaInfo.ContainerFormat, videoFormat, videoCodecID)
|
||||||
.Write();
|
.Write();
|
||||||
|
|
||||||
return result;
|
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)
|
private static decimal? FormatAudioChannelsFromAudioChannelPositions(MediaInfoModel mediaInfo)
|
||||||
{
|
{
|
||||||
var audioChannelPositions = mediaInfo.AudioChannelPositions;
|
if (mediaInfo.AudioChannelPositions == null)
|
||||||
var audioFormat = mediaInfo.AudioFormat;
|
|
||||||
|
|
||||||
if (audioChannelPositions.IsNullOrWhiteSpace())
|
|
||||||
{
|
{
|
||||||
return null;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
var match = PositionRegex.Match(mediaInfo.AudioChannelPositions);
|
||||||
|
if (match.Success)
|
||||||
{
|
{
|
||||||
if (audioChannelPositions.Contains("+"))
|
return decimal.Parse(match.Groups["position"].Value, NumberStyles.Number, CultureInfo.InvariantCulture);
|
||||||
{
|
|
||||||
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.Count() == 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 e)
|
|
||||||
{
|
|
||||||
Logger.Warn()
|
|
||||||
.Message("Unable to format audio channels using 'AudioChannelPositions', with a value of: '{0}' and '{1}'. Error {2}", audioChannelPositions, mediaInfo.AudioChannelPositionsTextContainer, e.Message)
|
|
||||||
.WriteSentryWarn("UnknownAudioChannelFormat", audioChannelPositions, mediaInfo.AudioChannelPositionsTextContainer)
|
|
||||||
.Write();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return 0;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetSceneNameMatch(string sceneName, params string[] tokens)
|
private static string GetSceneNameMatch(string sceneName, params string[] tokens)
|
||||||
|
@ -591,17 +304,21 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
|
|
||||||
public static string FormatVideoDynamicRange(MediaInfoModel mediaInfo)
|
public static string FormatVideoDynamicRange(MediaInfoModel mediaInfo)
|
||||||
{
|
{
|
||||||
return mediaInfo.GetHdrFormat() == HdrFormat.None ? "" : "HDR";
|
return mediaInfo.VideoHdrFormat != HdrFormat.None ? VideoDynamicRangeHdr : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string FormatVideoDynamicRangeType(MediaInfoModel mediaInfo)
|
public static string FormatVideoDynamicRangeType(MediaInfoModel mediaInfo)
|
||||||
{
|
{
|
||||||
switch (mediaInfo.GetHdrFormat())
|
switch (mediaInfo.VideoHdrFormat)
|
||||||
{
|
{
|
||||||
case HdrFormat.DolbyVision:
|
case HdrFormat.DolbyVision:
|
||||||
return "DV";
|
return "DV";
|
||||||
case HdrFormat.DolbyVisionHdr10:
|
case HdrFormat.DolbyVisionHdr10:
|
||||||
return "DV HDR10";
|
return "DV HDR10";
|
||||||
|
case HdrFormat.DolbyVisionHlg:
|
||||||
|
return "DV HLG";
|
||||||
|
case HdrFormat.DolbyVisionSdr:
|
||||||
|
return "DV SDR";
|
||||||
case HdrFormat.Hdr10:
|
case HdrFormat.Hdr10:
|
||||||
return "HDR10";
|
return "HDR10";
|
||||||
case HdrFormat.Hdr10Plus:
|
case HdrFormat.Hdr10Plus:
|
||||||
|
|
|
@ -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<string>();
|
|
||||||
|
|
||||||
// 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 Sonarr 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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +1,64 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Text.Json.Serialization;
|
||||||
using Newtonsoft.Json;
|
using FFMpegCore;
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles.MediaInfo
|
namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
{
|
{
|
||||||
public class MediaInfoModel : IEmbeddedDocument
|
public class MediaInfoModel : IEmbeddedDocument
|
||||||
{
|
{
|
||||||
public string ContainerFormat { get; set; }
|
public string RawStreamData { get; set; }
|
||||||
|
public string RawFrameData { 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 int SchemaRevision { get; set; }
|
public int SchemaRevision { get; set; }
|
||||||
|
|
||||||
|
public string ContainerFormat { get; set; }
|
||||||
|
public string VideoFormat { get; set; }
|
||||||
|
|
||||||
|
public string VideoCodecID { get; set; }
|
||||||
|
|
||||||
|
public string VideoProfile { get; set; }
|
||||||
|
|
||||||
|
public long 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 long 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<string> AudioLanguages { get; set; }
|
||||||
|
|
||||||
|
public List<string> Subtitles { get; set; }
|
||||||
|
|
||||||
|
public string ScanType { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
using System.Linq;
|
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles.MediaInfo
|
|
||||||
{
|
|
||||||
public static class MediaInfoModelExtensions
|
|
||||||
{
|
|
||||||
private const string HlgTransferFunction = "HLG";
|
|
||||||
private const string PgTransferFunction = "PQ";
|
|
||||||
private const string ValidHdrColourPrimaries = "BT.2020";
|
|
||||||
|
|
||||||
private static readonly string[] Hdr10Formats = { "SMPTE ST 2086", "HDR10" };
|
|
||||||
private static readonly string[] Hdr10PlusFormats = { "SMPTE ST 2094 App 4" };
|
|
||||||
private static readonly string[] DolbyVisionFormats = { "Dolby Vision" };
|
|
||||||
|
|
||||||
public static HdrFormat GetHdrFormat(this MediaInfoModel mediaInfo)
|
|
||||||
{
|
|
||||||
if (mediaInfo.VideoBitDepth < 10)
|
|
||||||
{
|
|
||||||
return HdrFormat.None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaInfo.VideoHdrFormat.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
if (DolbyVisionFormats.Any(mediaInfo.VideoHdrFormat.ContainsIgnoreCase))
|
|
||||||
{
|
|
||||||
if (Hdr10Formats.Any(mediaInfo.VideoHdrFormat.ContainsIgnoreCase))
|
|
||||||
{
|
|
||||||
return HdrFormat.DolbyVisionHdr10;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return HdrFormat.DolbyVision;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Hdr10Formats.Any(mediaInfo.VideoHdrFormat.ContainsIgnoreCase))
|
|
||||||
{
|
|
||||||
return HdrFormat.Hdr10;
|
|
||||||
}
|
|
||||||
else if (Hdr10PlusFormats.Any(mediaInfo.VideoHdrFormat.ContainsIgnoreCase))
|
|
||||||
{
|
|
||||||
return HdrFormat.Hdr10Plus;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We didn't match straight from the format from MediaInfo, so try and match in ColourPrimaries and TransferCharacteristics
|
|
||||||
if (mediaInfo.VideoColourPrimaries.IsNotNullOrWhiteSpace() && mediaInfo.VideoTransferCharacteristics.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
if (mediaInfo.VideoColourPrimaries.EqualsIgnoreCase(ValidHdrColourPrimaries))
|
|
||||||
{
|
|
||||||
if (mediaInfo.VideoTransferCharacteristics.EqualsIgnoreCase(PgTransferFunction))
|
|
||||||
{
|
|
||||||
return HdrFormat.Pq10;
|
|
||||||
}
|
|
||||||
else if (mediaInfo.VideoTransferCharacteristics.EqualsIgnoreCase(HlgTransferFunction))
|
|
||||||
{
|
|
||||||
return HdrFormat.Hlg10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaInfo.VideoHdrFormat.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
return HdrFormat.UnknownHdr; // MediaInfo reported Hdr information, but we're unsure of what type.
|
|
||||||
}
|
|
||||||
|
|
||||||
return HdrFormat.None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using FFMpegCore;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
@ -17,14 +19,33 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
{
|
{
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
private readonly List<FFProbePixelFormat> _pixelFormats;
|
||||||
|
|
||||||
public const int MINIMUM_MEDIA_INFO_SCHEMA_REVISION = 3;
|
public const int MINIMUM_MEDIA_INFO_SCHEMA_REVISION = 8;
|
||||||
public const int CURRENT_MEDIA_INFO_SCHEMA_REVISION = 7;
|
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)
|
public VideoFileInfoReader(IDiskProvider diskProvider, Logger logger)
|
||||||
{
|
{
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
_logger = logger;
|
_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<FFProbePixelFormat>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MediaInfoModel GetMediaInfo(string filename)
|
public MediaInfoModel GetMediaInfo(string filename)
|
||||||
|
@ -34,182 +55,76 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
throw new FileNotFoundException("Media file does not exist: " + filename);
|
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
|
// TODO: Cache media info by path, mtime and length so we don't need to read files multiple times
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
mediaInfo = new MediaInfo();
|
|
||||||
_logger.Debug("Getting media info from {0}", filename);
|
_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() ?? true)
|
||||||
{
|
{
|
||||||
// For .ts files we often have to scan more of the file to get all the info we need
|
ffprobeOutput = FFProbe.GetStreamJson(filename, ffOptions: new FFOptions { ExtraArguments = "-probesize 150000000 -analyzeduration 150000000" });
|
||||||
mediaInfo.Option("ParseSpeed", "0.3");
|
analysis = FFProbe.AnalyseStreamJson(ffprobeOutput);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mediaInfo.Option("ParseSpeed", "0.0");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int open;
|
var mediaInfoModel = new MediaInfoModel();
|
||||||
|
mediaInfoModel.ContainerFormat = analysis.Format.FormatName;
|
||||||
|
mediaInfoModel.VideoFormat = analysis.PrimaryVideoStream?.CodecName;
|
||||||
|
mediaInfoModel.VideoCodecID = analysis.PrimaryVideoStream?.CodecTagString;
|
||||||
|
mediaInfoModel.VideoProfile = analysis.PrimaryVideoStream?.Profile;
|
||||||
|
mediaInfoModel.VideoBitrate = analysis.PrimaryVideoStream?.BitRate ?? 0;
|
||||||
|
mediaInfoModel.VideoMultiViewCount = 1;
|
||||||
|
mediaInfoModel.VideoBitDepth = GetPixelFormat(analysis.PrimaryVideoStream?.PixelFormat)?.Components.Min(x => x.BitDepth) ?? 8;
|
||||||
|
mediaInfoModel.VideoColourPrimaries = analysis.PrimaryVideoStream?.ColorPrimaries;
|
||||||
|
mediaInfoModel.VideoTransferCharacteristics = analysis.PrimaryVideoStream?.ColorTransfer;
|
||||||
|
mediaInfoModel.DoviConfigurationRecord = analysis.PrimaryVideoStream?.SideDataList?.Find(x => x.GetType().Name == nameof(DoviConfigurationRecordSideData)) as DoviConfigurationRecordSideData;
|
||||||
|
mediaInfoModel.Height = analysis.PrimaryVideoStream?.Height ?? 0;
|
||||||
|
mediaInfoModel.Width = analysis.PrimaryVideoStream?.Width ?? 0;
|
||||||
|
mediaInfoModel.AudioFormat = analysis.PrimaryAudioStream?.CodecName;
|
||||||
|
mediaInfoModel.AudioCodecID = analysis.PrimaryAudioStream?.CodecTagString;
|
||||||
|
mediaInfoModel.AudioProfile = analysis.PrimaryAudioStream?.Profile;
|
||||||
|
mediaInfoModel.AudioBitrate = analysis.PrimaryAudioStream?.BitRate ?? 0;
|
||||||
|
mediaInfoModel.RunTime = GetBestRuntime(analysis.PrimaryAudioStream?.Duration, analysis.PrimaryVideoStream?.Duration, analysis.Format.Duration);
|
||||||
|
mediaInfoModel.AudioStreamCount = analysis.AudioStreams.Count;
|
||||||
|
mediaInfoModel.AudioChannels = analysis.PrimaryAudioStream?.Channels ?? 0;
|
||||||
|
mediaInfoModel.AudioChannelPositions = analysis.PrimaryAudioStream?.ChannelLayout;
|
||||||
|
mediaInfoModel.VideoFps = analysis.PrimaryVideoStream?.FrameRate ?? 0;
|
||||||
|
mediaInfoModel.AudioLanguages = analysis.AudioStreams?.Select(x => x.Language)
|
||||||
|
.Where(l => l.IsNotNullOrWhiteSpace())
|
||||||
|
.ToList();
|
||||||
|
mediaInfoModel.Subtitles = analysis.SubtitleStreams?.Select(x => x.Language)
|
||||||
|
.Where(l => l.IsNotNullOrWhiteSpace())
|
||||||
|
.ToList();
|
||||||
|
mediaInfoModel.ScanType = "Progressive";
|
||||||
|
mediaInfoModel.RawStreamData = ffprobeOutput;
|
||||||
|
mediaInfoModel.SchemaRevision = CURRENT_MEDIA_INFO_SCHEMA_REVISION;
|
||||||
|
|
||||||
using (var stream = _diskProvider.OpenReadStream(filename))
|
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))
|
||||||
{
|
{
|
||||||
open = mediaInfo.Open(stream);
|
var frameOutput = FFProbe.GetFrameJson(filename, ffOptions: new () { ExtraArguments = "-read_intervals \"%+#1\" -select_streams v" });
|
||||||
|
mediaInfoModel.RawFrameData = frameOutput;
|
||||||
|
|
||||||
|
frames = FFProbe.AnalyseFrameJson(frameOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (open != 0)
|
var streamSideData = analysis.PrimaryVideoStream?.SideDataList ?? new ();
|
||||||
{
|
var framesSideData = frames?.Frames?.Count > 0 ? frames?.Frames[0]?.SideDataList ?? new () : new ();
|
||||||
int audioRuntime;
|
|
||||||
int videoRuntime;
|
|
||||||
int generalRuntime;
|
|
||||||
|
|
||||||
// Runtime
|
var sideData = streamSideData.Concat(framesSideData).ToList();
|
||||||
int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "PlayTime"), out videoRuntime);
|
mediaInfoModel.VideoHdrFormat = GetHdrFormat(mediaInfoModel.VideoBitDepth, mediaInfoModel.VideoColourPrimaries, mediaInfoModel.VideoTransferCharacteristics, sideData);
|
||||||
int.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "PlayTime"), out audioRuntime);
|
|
||||||
int.TryParse(mediaInfo.Get(StreamKind.General, 0, "PlayTime"), out generalRuntime);
|
|
||||||
|
|
||||||
// Audio Channels
|
return mediaInfoModel;
|
||||||
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);
|
|
||||||
|
|
||||||
var audioChannelsStr = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
|
|
||||||
int.TryParse(audioChannelsStr, out audioChannels);
|
|
||||||
|
|
||||||
var 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");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "Unable to parse media info from file: {0}", filename);
|
_logger.Error(ex, "Unable to parse media info from file: {0}", filename);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
mediaInfo?.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -221,19 +136,80 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
return info?.RunTime;
|
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> sideData)
|
||||||
|
{
|
||||||
|
if (bitDepth < 10)
|
||||||
|
{
|
||||||
|
return HdrFormat.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryGetSideData<DoviConfigurationRecordSideData>(sideData, out var dovi))
|
||||||
|
{
|
||||||
|
return dovi.DvBlSignalCompatibilityId switch
|
||||||
|
{
|
||||||
|
1 => HdrFormat.DolbyVisionHdr10,
|
||||||
|
2 => HdrFormat.DolbyVisionSdr,
|
||||||
|
4 => HdrFormat.DolbyVisionHlg,
|
||||||
|
6 => HdrFormat.DolbyVisionHdr10,
|
||||||
|
_ => HdrFormat.DolbyVision
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ValidHdrColourPrimaries.Contains(colorPrimaries) || !ValidHdrTransferFunctions.Contains(transferFunction))
|
||||||
|
{
|
||||||
|
return HdrFormat.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HlgTransferFunctions.Contains(transferFunction))
|
||||||
|
{
|
||||||
|
return HdrFormat.Hlg10;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PqTransferFunctions.Contains(transferFunction))
|
||||||
|
{
|
||||||
|
if (TryGetSideData<HdrDynamicMetadataSpmte2094>(sideData, out _))
|
||||||
|
{
|
||||||
|
return HdrFormat.Hdr10Plus;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryGetSideData<MasteringDisplayMetadata>(sideData, out _) ||
|
||||||
|
TryGetSideData<ContentLightLevelMetadata>(sideData, out _))
|
||||||
|
{
|
||||||
|
return HdrFormat.Hdr10;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HdrFormat.Pq10;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HdrFormat.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetSideData<T>(List<SideData> list, out T result)
|
||||||
|
where T : SideData
|
||||||
|
{
|
||||||
|
result = (T)list?.FirstOrDefault(x => x.GetType().Name == typeof(T).Name);
|
||||||
|
|
||||||
|
return result != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -194,11 +194,11 @@ namespace NzbDrone.Core.Notifications.Discord
|
||||||
break;
|
break;
|
||||||
case DiscordImportFieldType.Languages:
|
case DiscordImportFieldType.Languages:
|
||||||
discordField.Name = "Languages";
|
discordField.Name = "Languages";
|
||||||
discordField.Value = message.EpisodeFile.MediaInfo.AudioLanguages;
|
discordField.Value = message.EpisodeFile.MediaInfo.AudioLanguages.ConcatToString("/");
|
||||||
break;
|
break;
|
||||||
case DiscordImportFieldType.Subtitles:
|
case DiscordImportFieldType.Subtitles:
|
||||||
discordField.Name = "Subtitles";
|
discordField.Name = "Subtitles";
|
||||||
discordField.Value = message.EpisodeFile.MediaInfo.Subtitles;
|
discordField.Value = message.EpisodeFile.MediaInfo.Subtitles.ConcatToString("/");
|
||||||
break;
|
break;
|
||||||
case DiscordImportFieldType.Release:
|
case DiscordImportFieldType.Release:
|
||||||
discordField.Name = "Release";
|
discordField.Name = "Release";
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -86,6 +87,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);
|
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<string, string> Iso639BTMap = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "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,
|
public FileNameBuilder(INamingConfigService namingConfigService,
|
||||||
IQualityDefinitionService qualityDefinitionService,
|
IQualityDefinitionService qualityDefinitionService,
|
||||||
ICacheManager cacheManager,
|
ICacheManager cacheManager,
|
||||||
|
@ -616,7 +642,7 @@ namespace NzbDrone.Core.Organizer
|
||||||
new Dictionary<string, int>(FileNameBuilderTokenEqualityComparer.Instance)
|
new Dictionary<string, int>(FileNameBuilderTokenEqualityComparer.Instance)
|
||||||
{
|
{
|
||||||
{ MediaInfoVideoDynamicRangeToken, 5 },
|
{ MediaInfoVideoDynamicRangeToken, 5 },
|
||||||
{ MediaInfoVideoDynamicRangeTypeToken, 5 }
|
{ MediaInfoVideoDynamicRangeTypeToken, 8 }
|
||||||
};
|
};
|
||||||
|
|
||||||
private void AddMediaInfoTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, EpisodeFile episodeFile)
|
private void AddMediaInfoTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, EpisodeFile episodeFile)
|
||||||
|
@ -630,13 +656,13 @@ namespace NzbDrone.Core.Organizer
|
||||||
|
|
||||||
var sceneName = episodeFile.GetSceneOrFileName();
|
var sceneName = episodeFile.GetSceneOrFileName();
|
||||||
|
|
||||||
var videoCodec = MediaInfoFormatter.FormatVideoCodec(episodeFile.MediaInfo, sceneName);
|
var videoCodec = MediaInfoFormatter.FormatVideoCodec(episodeFile.MediaInfo, sceneName);
|
||||||
var audioCodec = MediaInfoFormatter.FormatAudioCodec(episodeFile.MediaInfo, sceneName);
|
var audioCodec = MediaInfoFormatter.FormatAudioCodec(episodeFile.MediaInfo, sceneName);
|
||||||
var audioChannels = MediaInfoFormatter.FormatAudioChannels(episodeFile.MediaInfo);
|
var audioChannels = MediaInfoFormatter.FormatAudioChannels(episodeFile.MediaInfo);
|
||||||
var audioLanguages = episodeFile.MediaInfo.AudioLanguages ?? string.Empty;
|
var audioLanguages = episodeFile.MediaInfo.AudioLanguages ?? new List<string>();
|
||||||
var subtitles = episodeFile.MediaInfo.Subtitles ?? string.Empty;
|
var subtitles = episodeFile.MediaInfo.Subtitles ?? new List<string>();
|
||||||
|
|
||||||
var videoBitDepth = episodeFile.MediaInfo.VideoBitDepth > 0 ? episodeFile.MediaInfo.VideoBitDepth.ToString() : string.Empty;
|
var videoBitDepth = episodeFile.MediaInfo.VideoBitDepth > 0 ? episodeFile.MediaInfo.VideoBitDepth.ToString() : 8.ToString();
|
||||||
var audioChannelsFormatted = audioChannels > 0 ?
|
var audioChannelsFormatted = audioChannels > 0 ?
|
||||||
audioChannels.ToString("F1", CultureInfo.InvariantCulture) :
|
audioChannels.ToString("F1", CultureInfo.InvariantCulture) :
|
||||||
string.Empty;
|
string.Empty;
|
||||||
|
@ -701,42 +727,29 @@ namespace NzbDrone.Core.Organizer
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetLanguagesToken(string mediaInfoLanguages, string filter, bool skipEnglishOnly, bool quoted)
|
private string GetLanguagesToken(List<string> mediaInfoLanguages, string filter, bool skipEnglishOnly, bool quoted)
|
||||||
{
|
{
|
||||||
List<string> tokens = new List<string>();
|
var tokens = new List<string>();
|
||||||
foreach (var item in mediaInfoLanguages.Split('/'))
|
foreach (var item in mediaInfoLanguages)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(item))
|
if (!string.IsNullOrWhiteSpace(item) && item != "und")
|
||||||
{
|
{
|
||||||
tokens.Add(item.Trim());
|
tokens.Add(item.Trim());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var cultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures);
|
|
||||||
for (int i = 0; i < tokens.Count; i++)
|
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
|
try
|
||||||
{
|
{
|
||||||
var cultureInfo = cultures.FirstOrDefault(p => p.EnglishName.RemoveAccent() == tokens[i]);
|
var token = tokens[i].ToLowerInvariant();
|
||||||
|
if (Iso639BTMap.TryGetValue(token, out var mapped))
|
||||||
if (cultureInfo != null)
|
|
||||||
{
|
{
|
||||||
tokens[i] = cultureInfo.TwoLetterISOLanguageName.ToUpper();
|
token = mapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cultureInfo = new CultureInfo(token);
|
||||||
|
tokens[i] = cultureInfo.TwoLetterISOLanguageName.ToUpper();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
|
@ -101,28 +101,29 @@ namespace NzbDrone.Core.Organizer
|
||||||
|
|
||||||
var mediaInfo = new MediaInfoModel()
|
var mediaInfo = new MediaInfoModel()
|
||||||
{
|
{
|
||||||
VideoCodec = "AVC",
|
VideoFormat = "AVC",
|
||||||
VideoBitDepth = 10,
|
VideoBitDepth = 10,
|
||||||
VideoColourPrimaries = "BT.2020",
|
VideoMultiViewCount = 2,
|
||||||
|
VideoColourPrimaries = "bt2020",
|
||||||
VideoTransferCharacteristics = "HLG",
|
VideoTransferCharacteristics = "HLG",
|
||||||
AudioFormat = "DTS",
|
AudioFormat = "DTS",
|
||||||
AudioChannelsContainer = 6,
|
AudioChannels = 6,
|
||||||
AudioChannelPositions = "3/2/0.1",
|
AudioChannelPositions = "5.1",
|
||||||
AudioLanguages = "English",
|
AudioLanguages = new List<string> { "ger" },
|
||||||
Subtitles = "English/German"
|
Subtitles = new List<string> { "eng", "ger" }
|
||||||
};
|
};
|
||||||
|
|
||||||
var mediaInfoAnime = new MediaInfoModel()
|
var mediaInfoAnime = new MediaInfoModel()
|
||||||
{
|
{
|
||||||
VideoCodec = "AVC",
|
VideoFormat = "AVC",
|
||||||
VideoBitDepth = 10,
|
VideoBitDepth = 10,
|
||||||
VideoColourPrimaries = "BT.2020",
|
VideoColourPrimaries = "BT.2020",
|
||||||
VideoTransferCharacteristics = "HLG",
|
VideoTransferCharacteristics = "HLG",
|
||||||
AudioFormat = "DTS",
|
AudioFormat = "DTS",
|
||||||
AudioChannelsContainer = 6,
|
AudioChannels = 6,
|
||||||
AudioChannelPositions = "3/2/0.1",
|
AudioChannelPositions = "5.1",
|
||||||
AudioLanguages = "Japanese",
|
AudioLanguages = new List<string> { "jpn" },
|
||||||
Subtitles = "Japanese/English"
|
Subtitles = new List<string> { "jpn", "eng" }
|
||||||
};
|
};
|
||||||
|
|
||||||
_singleEpisodeFile = new EpisodeFile
|
_singleEpisodeFile = new EpisodeFile
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices.ComTypes;
|
||||||
using NzbDrone.Core.Languages;
|
using NzbDrone.Core.Languages;
|
||||||
|
using NzbDrone.Core.Organizer;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Parser
|
namespace NzbDrone.Core.Parser
|
||||||
{
|
{
|
||||||
|
@ -52,6 +54,11 @@ namespace NzbDrone.Core.Parser
|
||||||
else if (isoCode.Length == 3)
|
else if (isoCode.Length == 3)
|
||||||
{
|
{
|
||||||
//Lookup ISO639-2T code
|
//Lookup ISO639-2T code
|
||||||
|
if (FileNameBuilder.Iso639BTMap.TryGetValue(isoCode, out var mapped))
|
||||||
|
{
|
||||||
|
isoCode = mapped;
|
||||||
|
}
|
||||||
|
|
||||||
return All.SingleOrDefault(l => l.ThreeLetterCode == isoCode);
|
return All.SingleOrDefault(l => l.ThreeLetterCode == isoCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Dapper" Version="2.0.123" />
|
<PackageReference Include="Dapper" Version="2.0.123" />
|
||||||
<PackageReference Include="MailKit" Version="2.10.1" />
|
<PackageReference Include="MailKit" Version="2.10.1" />
|
||||||
|
<PackageReference Include="Servarr.FFMpegCore" Version="4.7.0-26" />
|
||||||
|
<PackageReference Include="Servarr.FFprobe" Version="5.0.1.91" />
|
||||||
<PackageReference Include="System.Memory" Version="4.5.4" />
|
<PackageReference Include="System.Memory" Version="4.5.4" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||||
|
|
|
@ -125,10 +125,11 @@ namespace NzbDrone.Update.UpdateEngine
|
||||||
_logger.Info("Copying new files to target folder");
|
_logger.Info("Copying new files to target folder");
|
||||||
_diskTransferService.MirrorFolder(_appFolderInfo.GetUpdatePackageFolder(), installationFolder);
|
_diskTransferService.MirrorFolder(_appFolderInfo.GetUpdatePackageFolder(), installationFolder);
|
||||||
|
|
||||||
// Set executable flag on app
|
// Set executable flag on app and ffprobe
|
||||||
if (OsInfo.IsOsx || (OsInfo.IsLinux && PlatformInfo.IsNetCore))
|
if (OsInfo.IsOsx || (OsInfo.IsLinux && PlatformInfo.IsNetCore))
|
||||||
{
|
{
|
||||||
_diskProvider.SetFilePermissions(Path.Combine(installationFolder, "Sonarr"), "755", null);
|
_diskProvider.SetFilePermissions(Path.Combine(installationFolder, "Sonarr"), "755", null);
|
||||||
|
_diskProvider.SetFilePermissions(Path.Combine(installationFolder, "ffprobe"), "755", null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<configuration>
|
|
||||||
<dllmap os="osx" dll="mediainfo" target="libmediainfo.dylib"/>
|
|
||||||
<dllmap os="linux" dll="mediainfo" target="libmediainfo.so.0" />
|
|
||||||
<dllmap os="freebsd" dll="mediainfo" target="libmediainfo.so.0" />
|
|
||||||
<dllmap os="solaris" dll="mediainfo" target="libmediainfo.so.0.0.0" />
|
|
||||||
</configuration>
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,5 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||||
using Sonarr.Http.REST;
|
using Sonarr.Http.REST;
|
||||||
|
|
||||||
|
@ -7,13 +7,13 @@ namespace Sonarr.Api.V3.EpisodeFiles
|
||||||
{
|
{
|
||||||
public class MediaInfoResource : RestResource
|
public class MediaInfoResource : RestResource
|
||||||
{
|
{
|
||||||
public int AudioBitrate { get; set; }
|
public long AudioBitrate { get; set; }
|
||||||
public decimal AudioChannels { get; set; }
|
public decimal AudioChannels { get; set; }
|
||||||
public string AudioCodec { get; set; }
|
public string AudioCodec { get; set; }
|
||||||
public string AudioLanguages { get; set; }
|
public string AudioLanguages { get; set; }
|
||||||
public int AudioStreamCount { get; set; }
|
public int AudioStreamCount { get; set; }
|
||||||
public int VideoBitDepth { get; set; }
|
public int VideoBitDepth { get; set; }
|
||||||
public int VideoBitrate { get; set; }
|
public long VideoBitrate { get; set; }
|
||||||
public string VideoCodec { get; set; }
|
public string VideoCodec { get; set; }
|
||||||
public decimal VideoFps { get; set; }
|
public decimal VideoFps { get; set; }
|
||||||
public string VideoDynamicRange { get; set; }
|
public string VideoDynamicRange { get; set; }
|
||||||
|
@ -34,23 +34,23 @@ namespace Sonarr.Api.V3.EpisodeFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MediaInfoResource
|
return new MediaInfoResource
|
||||||
{
|
{
|
||||||
AudioBitrate = model.AudioBitrate,
|
AudioBitrate = model.AudioBitrate,
|
||||||
AudioChannels = MediaInfoFormatter.FormatAudioChannels(model),
|
AudioChannels = MediaInfoFormatter.FormatAudioChannels(model),
|
||||||
AudioLanguages = model.AudioLanguages,
|
AudioLanguages = model.AudioLanguages.ConcatToString("/"),
|
||||||
AudioStreamCount = model.AudioStreamCount,
|
AudioStreamCount = model.AudioStreamCount,
|
||||||
AudioCodec = MediaInfoFormatter.FormatAudioCodec(model, sceneName),
|
AudioCodec = MediaInfoFormatter.FormatAudioCodec(model, sceneName),
|
||||||
VideoBitDepth = model.VideoBitDepth,
|
VideoBitDepth = model.VideoBitDepth,
|
||||||
VideoBitrate = model.VideoBitrate,
|
VideoBitrate = model.VideoBitrate,
|
||||||
VideoCodec = MediaInfoFormatter.FormatVideoCodec(model, sceneName),
|
VideoCodec = MediaInfoFormatter.FormatVideoCodec(model, sceneName),
|
||||||
VideoFps = model.VideoFps,
|
VideoFps = Math.Round(model.VideoFps, 3),
|
||||||
VideoDynamicRange = MediaInfoFormatter.FormatVideoDynamicRange(model),
|
VideoDynamicRange = MediaInfoFormatter.FormatVideoDynamicRange(model),
|
||||||
VideoDynamicRangeType = MediaInfoFormatter.FormatVideoDynamicRangeType(model),
|
VideoDynamicRangeType = MediaInfoFormatter.FormatVideoDynamicRangeType(model),
|
||||||
Resolution = $"{model.Width}x{model.Height}",
|
Resolution = $"{model.Width}x{model.Height}",
|
||||||
RunTime = FormatRuntime(model.RunTime),
|
RunTime = FormatRuntime(model.RunTime),
|
||||||
ScanType = model.ScanType,
|
ScanType = model.ScanType,
|
||||||
Subtitles = model.Subtitles
|
Subtitles = model.Subtitles.ConcatToString("/")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FormatRuntime(TimeSpan runTime)
|
private static string FormatRuntime(TimeSpan runTime)
|
||||||
|
|
Loading…
Reference in New Issue