New: Refactor MediaInfo tokens (fixes old tokens adds new stuff) (#3058)

* Rename all 'episodeFile' variables to 'movieFile'

* Improve media info extraction with more fields

* Improve media info tokens extraction

* Add missing fields to MediaInfoModel

* Restore to previous implementation of null handling

* Forgot to add MediaInfoFormatter to project

* Add missing EqualsIgnoreCase extension method

* Simplify Logger.Debug() invocations

* Add missing StartsWithIgnoreCase extension method

* This '.Value' shouldn't be required

* Remove TODO comment

* Upgrade MediaInfo from 17.10 to 18.08.1

* Use correct media info field for files listing

* Replace media info "VideoCodec" (deprecated) with "VideoFormat"

* Fix 'Formatiting' typos

* Add support for media info Format_AdditionalFeatures' field

* Add proper support for all DTS and TrueHD flavors

* Add support for '3D' media info token

* Remove deprecated media info video/audio profile fields

* Add support for 'HDR' media info token

* Add new video parameters to anime file name sample

* Adapt tests for new media info fields

* Revert "Remove deprecated media info video/audio profile fields"

* Include missing test files in core test project

* Fix small regression issue

* Allow sample movie to be detected as HDR

* Do not parse audio channel positions if there are no channels

* Clean up extra blank line

* Reuse already declared variable

* Fix wrong audio channels detection on DTS:X streams

* Fix all failing unit tests

* Fix remaining failing unit tests
pull/2/head
Ricardo Amaral 6 years ago committed by Leonardo Galli
parent 4009852c35
commit 97f111bec8

@ -78,6 +78,21 @@ namespace NzbDrone.Common.Extensions
return !string.IsNullOrWhiteSpace(text);
}
public static bool StartsWithIgnoreCase(this string text, string startsWith)
{
return text.StartsWith(startsWith, StringComparison.InvariantCultureIgnoreCase);
}
public static bool EndsWithIgnoreCase(this string text, string startsWith)
{
return text.EndsWith(startsWith, StringComparison.InvariantCultureIgnoreCase);
}
public static bool EqualsIgnoreCase(this string text, string equals)
{
return text.Equals(equals, StringComparison.InvariantCultureIgnoreCase);
}
public static bool ContainsIgnoreCase(this string text, string contains)
{
return text.IndexOf(contains, StringComparison.InvariantCultureIgnoreCase) > -1;
@ -118,4 +133,4 @@ namespace NzbDrone.Common.Extensions
return Encoding.ASCII.GetString(new [] { byteResult });
}
}
}
}

@ -0,0 +1,177 @@
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
{
AudioChannels = 6,
AudioChannelPositions = null,
AudioChannelPositionsText = "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
{
AudioChannels = 2,
AudioChannelPositions = null,
AudioChannelPositionsText = "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
{
AudioChannels = 2,
AudioChannelPositions = null,
AudioChannelPositionsText = 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
{
AudioChannels = 2,
AudioChannelPositions = null,
AudioChannelPositionsText = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
}
[Test]
public void should_sum_AudioChannelPositions()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannels = 2,
AudioChannelPositions = "2/0/0",
AudioChannelPositionsText = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
}
[Test]
public void should_sum_AudioChannelPositions_including_decimal()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannels = 2,
AudioChannelPositions = "3/2/0.1",
AudioChannelPositionsText = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
}
[Test]
public void should_cleanup_extraneous_text_from_AudioChannelPositions()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannels = 2,
AudioChannelPositions = "Object Based / 3/2/2.1",
AudioChannelPositionsText = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
}
[Test]
public void should_skip_empty_groups_in_AudioChannelPositions()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannels = 2,
AudioChannelPositions = " / 2/0/0.0",
AudioChannelPositionsText = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
}
[Test]
public void should_sum_first_series_of_numbers_from_AudioChannelPositions()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannels = 2,
AudioChannelPositions = "3/2/2.1 / 3/2/2.1",
AudioChannelPositionsText = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
}
[Test]
public void should_sum_dual_mono_representation_AudioChannelPositions()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannels = 2,
AudioChannelPositions = "1+1",
AudioChannelPositionsText = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2.0m);
}
[Test]
public void should_use_AudioChannelPositionText_when_AudioChannelChannelPosition_is_invalid()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannels = 6,
AudioChannelPositions = "15 objects",
AudioChannelPositionsText = "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
{
AudioChannels = 2,
AudioChannelPositions = "15 objects / 3/2.1",
AudioChannelPositionsText = null,
SchemaRevision = 3
};
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
}
}
}

@ -0,0 +1,73 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
{
[TestFixture]
public class FormatAudioCodecFixture : TestBase
{
private static string sceneName = "My.Series.S01E01-Sonarr";
[TestCase("AC-3", "AC3")]
[TestCase("E-AC-3", "EAC3")]
[TestCase("MPEG Audio", "MPEG Audio")]
[TestCase("DTS", "DTS")]
public void should_format_audio_format_legacy(string audioFormat, string expectedFormat)
{
var mediaInfoModel = new MediaInfoModel
{
AudioFormat = audioFormat
};
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
}
[TestCase("MPEG Audio, A_MPEG/L2, , ", "droned.s01e03.swedish.720p.hdtv.x264-prince", "MP2")]
[TestCase("Vorbis, A_VORBIS, , Xiph.Org libVorbis I 20101101 (Schaufenugget)", "DB Super HDTV", "Vorbis")]
[TestCase("PCM, 1, , ", "DW DVDRip XviD-idTV", "PCM")] // Dubbed most likely
[TestCase("TrueHD, A_TRUEHD, , ", "", "TrueHD")]
[TestCase("WMA, 161, , ", "Droned.wmv", "WMA")]
[TestCase("WMA, 162, Pro, ", "B.N.S04E18.720p.WEB-DL", "WMA")]
[TestCase("Opus, A_OPUS, , ", "Roadkill Ep3x11 - YouTube.webm", "Opus")]
[TestCase("mp3 , 0, , ", "climbing.mp4", "MP3")]
public void should_format_audio_format(string audioFormatPack, string sceneName, string expectedFormat)
{
var split = audioFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
var mediaInfoModel = new MediaInfoModel
{
AudioFormat = split[0],
AudioCodecID = split[1],
AudioProfile = split[2],
AudioCodecLibrary = split[3]
};
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
}
[Test]
public void should_return_MP3_for_MPEG_Audio_with_Layer_3_for_the_profile()
{
var mediaInfoModel = new MediaInfoModel
{
AudioFormat = "MPEG Audio",
AudioProfile = "Layer 3"
};
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel, sceneName).Should().Be("MP3");
}
[Test]
public void should_return_AudioFormat_by_default()
{
var mediaInfoModel = new MediaInfoModel
{
AudioFormat = "Other Audio Format",
AudioCodecID = "Other Audio Codec"
};
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel, sceneName).Should().Be(mediaInfoModel.AudioFormat);
}
}
}

@ -0,0 +1,92 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
{
[TestFixture]
public class FormatVideoCodecFixture : TestBase
{
[TestCase("AVC", null, "h264")]
[TestCase("AVC", "source.title.x264.720p-Sonarr", "x264")]
[TestCase("AVC", "source.title.h264.720p-Sonarr", "h264")]
[TestCase("V_MPEGH/ISO/HEVC", null, "h265")]
[TestCase("V_MPEGH/ISO/HEVC", "source.title.x265.720p-Sonarr", "x265")]
[TestCase("V_MPEGH/ISO/HEVC", "source.title.h265.720p-Sonarr", "h265")]
[TestCase("MPEG-2 Video", null, "MPEG2")]
public void should_format_video_codec_with_source_title_legacy(string videoCodec, string sceneName, string expectedFormat)
{
var mediaInfoModel = new MediaInfoModel
{
VideoFormat = videoCodec
};
MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
}
[TestCase("MPEG Video, 2, Main@High, ", "Droned.S01E02.1080i.HDTV.DD5.1.MPEG2-NTb", "MPEG2")]
[TestCase("MPEG Video, V_MPEG2, Main@High, ", "", "MPEG2")]
[TestCase("MPEG Video, , , ", "The.Simpsons.S13E04.INTERNAL-ANiVCD.mpg", "MPEG")]
[TestCase("VC-1, WVC1, Advanced@L4, ", "B.N.S04E18.720p.WEB-DL", "VC1")]
[TestCase("VC-1, V_MS/VFW/FOURCC / WVC1, Advanced@L3, ", "", "VC1")]
[TestCase("VC-1, WMV3, MP@LL, ", "It's Always Sunny S07E13 The Gang's RevengeHDTV.XviD-2HD.avi", "VC1")]
[TestCase("V.MPEG4/ISO/AVC, V.MPEG4/ISO/AVC, , ", "pd.2015.S03E08.720p.iP.WEBRip.AAC2.0.H264-BTW", "h264")]
[TestCase("WMV2, WMV2, , ", "Droned.wmv", "WMV")]
[TestCase("xvid, xvid, , ", "", "XviD")]
[TestCase("div3, div3, , ", "spsm.dvdrip.divx.avi'.", "DivX")]
[TestCase("VP6, 4, , ", "Top Gear - S12E01 - Lorries - SD TV.flv", "VP6")]
[TestCase("VP7, VP70, General, ", "Sweet Seymour.avi", "VP7")]
[TestCase("VP8, V_VP8, , ", "Dick.mkv", "VP8")]
[TestCase("VP9, V_VP9, , ", "Roadkill Ep3x11 - YouTube.webm", "VP9")]
[TestCase("x264, x264, , ", "Ghost Advent - S04E05 - Stanley Hotel SDTV.avi", "x264")]
[TestCase("V_MPEGH/ISO/HEVC, V_MPEGH/ISO/HEVC, , ", "The BBT S11E12 The Matrimonial Metric 1080p 10bit AMZN WEB-DL", "h265")]
[TestCase("MPEG-4 Visual, 20, Simple@L1, Lavc52.29.0", "Will.And.Grace.S08E14.WS.DVDrip.XviD.I.Love.L.Gay-Obfuscated", "XviD")]
[TestCase("MPEG-4 Visual, 20, Advanced Simple@L5, XviD0046", "", "XviD")]
[TestCase("mp4v, mp4v, , ", "American.Chopper.S06E07.Mountain.Creek.Bike.DSR.XviD-KRS", "XviD")]
public void should_format_video_format(string videoFormatPack, string sceneName, string expectedFormat)
{
var split = videoFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
var mediaInfoModel = new MediaInfoModel
{
VideoFormat = split[0],
VideoCodecID = split[1],
VideoProfile = split[2],
VideoCodecLibrary = split[3]
};
MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
}
[TestCase("AVC, AVC, , x264", "Some.Video.S01E01.h264", "x264")] // Force mediainfo tag
[TestCase("HEVC, HEVC, , x265", "Some.Video.S01E01.h265", "x265")] // Force mediainfo tag
[TestCase("AVC, AVC, , ", "Some.Video.S01E01.x264", "x264")] // Not seen in practice, but honor tag if otherwise unknown
[TestCase("HEVC, HEVC, , ", "Some.Video.S01E01.x265", "x265")] // Not seen in practice, but honor tag if otherwise unknown
[TestCase("AVC, AVC, , ", "Some.Video.S01E01", "h264")] // Default value
[TestCase("HEVC, HEVC, , ", "Some.Video.S01E01", "h265")] // Default value
public void should_format_video_format_fallbacks(string videoFormatPack, string sceneName, string expectedFormat)
{
var split = videoFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
var mediaInfoModel = new MediaInfoModel
{
VideoFormat = split[0],
VideoCodecID = split[1],
VideoProfile = split[2],
VideoCodecLibrary = split[3]
};
MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
}
[Test]
public void should_return_VideoFormat_by_default()
{
var mediaInfoModel = new MediaInfoModel
{
VideoFormat = "VideoCodec"
};
MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, null).Should().Be(mediaInfoModel.VideoFormat);
}
}
}

@ -16,15 +16,15 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
[TestFixture]
public class UpdateMediaInfoServiceFixture : CoreTest<UpdateMediaInfoService>
{
private Movie _series;
private Movie _movie;
[SetUp]
public void Setup()
{
_series = new Movie
_movie = new Movie
{
Id = 1,
Path = @"C:\series".AsOsAgnostic()
Path = @"C:\movie".AsOsAgnostic()
};
Mocker.GetMock<IConfigService>()
@ -60,7 +60,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
.All()
.With(v => v.RelativePath = "media.mkv")
.TheFirst(1)
.With(v => v.MediaInfo = new MediaInfoModel { SchemaRevision = 3 })
.With(v => v.MediaInfo = new MediaInfoModel { SchemaRevision = VideoFileInfoReader.CURRENT_MEDIA_INFO_SCHEMA_REVISION })
.BuildList();
Mocker.GetMock<IMediaFileService>()
@ -70,10 +70,36 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
GivenFileExists();
GivenSuccessfulScan();
Subject.Handle(new MovieScannedEvent(_series));
Subject.Handle(new MovieScannedEvent(_movie));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(Path.Combine(_series.Path, "media.mkv")), Times.Exactly(2));
.Verify(v => v.GetMediaInfo(Path.Combine(_movie.Path, "media.mkv")), Times.Exactly(2));
Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<MovieFile>()), Times.Exactly(2));
}
[Test]
public void should_skip_not_yet_date_media_info()
{
var episodeFiles = Builder<MovieFile>.CreateListOfSize(3)
.All()
.With(v => v.RelativePath = "media.mkv")
.TheFirst(1)
.With(v => v.MediaInfo = new MediaInfoModel { SchemaRevision = VideoFileInfoReader.MINIMUM_MEDIA_INFO_SCHEMA_REVISION })
.BuildList();
Mocker.GetMock<IMediaFileService>()
.Setup(v => v.GetFilesByMovie(1))
.Returns(episodeFiles);
GivenFileExists();
GivenSuccessfulScan();
Subject.Handle(new MovieScannedEvent(_movie));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(Path.Combine(_movie.Path, "media.mkv")), Times.Exactly(2));
Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<MovieFile>()), Times.Exactly(2));
@ -96,10 +122,10 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
GivenFileExists();
GivenSuccessfulScan();
Subject.Handle(new MovieScannedEvent(_series));
Subject.Handle(new MovieScannedEvent(_movie));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(Path.Combine(_series.Path, "media.mkv")), Times.Exactly(3));
.Verify(v => v.GetMediaInfo(Path.Combine(_movie.Path, "media.mkv")), Times.Exactly(3));
Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<MovieFile>()), Times.Exactly(3));
@ -119,7 +145,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
GivenSuccessfulScan();
Subject.Handle(new MovieScannedEvent(_series));
Subject.Handle(new MovieScannedEvent(_movie));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo("media.mkv"), Times.Never());
@ -144,12 +170,12 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
GivenFileExists();
GivenSuccessfulScan();
GivenFailedScan(Path.Combine(_series.Path, "media2.mkv"));
GivenFailedScan(Path.Combine(_movie.Path, "media2.mkv"));
Subject.Handle(new MovieScannedEvent(_series));
Subject.Handle(new MovieScannedEvent(_movie));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(Path.Combine(_series.Path, "media.mkv")), Times.Exactly(1));
.Verify(v => v.GetMediaInfo(Path.Combine(_movie.Path, "media.mkv")), Times.Exactly(1));
Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<MovieFile>()), Times.Exactly(1));

@ -31,10 +31,8 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "H264_sample.mp4");
Subject.GetRunTime(path).Seconds.Should().Be(10);
}
[Test]
public void get_info()
{
@ -42,21 +40,27 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
var info = Subject.GetMediaInfo(path);
info.VideoFormat.Should().Be("AVC");
info.VideoCodecID.Should().Be("avc1");
info.VideoProfile.Should().Be("Baseline@L2.1");
info.VideoCodecLibrary.Should().Be("");
info.VideoMultiViewCount.Should().Be(0);
info.VideoColourPrimaries.Should().Be("BT.601 NTSC");
info.VideoTransferCharacteristics.Should().Be("BT.709");
info.AudioFormat.Should().Be("AAC");
info.AudioCodecID.Should().BeOneOf("40", "mp4a-40-2");
info.AudioCodecLibrary.Should().Be("");
info.AudioBitrate.Should().Be(128000);
info.AudioChannels.Should().Be(2);
info.AudioFormat.Should().Be("AAC");
info.AudioLanguages.Should().Be("English");
info.AudioProfile.Should().Be("LC");
info.AudioAdditionalFeatures.Should().Be("");
info.Height.Should().Be(320);
info.RunTime.Seconds.Should().Be(10);
info.ScanType.Should().Be("Progressive");
info.Subtitles.Should().Be("");
info.VideoBitrate.Should().Be(193329);
info.VideoCodec.Should().Be("AVC");
info.VideoFps.Should().Be(24);
info.Width.Should().Be(480);
}
[Test]
@ -73,20 +77,27 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
var info = Subject.GetMediaInfo(path);
info.VideoFormat.Should().Be("AVC");
info.VideoCodecID.Should().Be("avc1");
info.VideoProfile.Should().Be("Baseline@L2.1");
info.VideoCodecLibrary.Should().Be("");
info.VideoMultiViewCount.Should().Be(0);
info.VideoColourPrimaries.Should().Be("BT.601 NTSC");
info.VideoTransferCharacteristics.Should().Be("BT.709");
info.AudioFormat.Should().Be("AAC");
info.AudioCodecID.Should().BeOneOf("40", "mp4a-40-2");
info.AudioCodecLibrary.Should().Be("");
info.AudioBitrate.Should().Be(128000);
info.AudioChannels.Should().Be(2);
info.AudioFormat.Should().Be("AAC");
info.AudioLanguages.Should().Be("English");
info.AudioProfile.Should().Be("LC");
info.AudioAdditionalFeatures.Should().Be("");
info.Height.Should().Be(320);
info.RunTime.Seconds.Should().Be(10);
info.ScanType.Should().Be("Progressive");
info.Subtitles.Should().Be("");
info.VideoBitrate.Should().Be(193329);
info.VideoCodec.Should().Be("AVC");
info.VideoFps.Should().Be(24);
info.Width.Should().Be(480);
}
[Test]
@ -100,23 +111,5 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
stream.Close();
}
[Test]
[TestCase("/ Front: L R", 2.0)]
public void should_correctly_read_audio_channels(string ChannelPositions, decimal formattedChannels)
{
var info = new MediaInfoModel()
{
VideoCodec = "AVC",
AudioFormat = "DTS",
AudioLanguages = "English",
Subtitles = "English",
AudioChannels = 2,
AudioChannelPositions = ChannelPositions,
SchemaRevision = 3,
};
info.FormattedAudioChannels.Should().Be(formattedChannels);
}
}
}
}

@ -314,6 +314,9 @@
<Compile Include="OrganizerTests\CleanFixture.cs" />
<Compile Include="MediaFiles\MediaFileServiceTests\FilterFixture.cs" />
<Compile Include="MediaFiles\MediaFileTableCleanupServiceFixture.cs" />
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatterTests\FormatAudioChannelsFixture.cs" />
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatterTests\FormatAudioCodecFixture.cs" />
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatterTests\FormatVideoCodecFixture.cs" />
<Compile Include="MediaFiles\MediaInfo\UpdateMediaInfoServiceFixture.cs" />
<Compile Include="MediaFiles\MediaInfo\VideoFileInfoReaderFixture.cs" />
<Compile Include="MediaFiles\RenameMovieFileServiceFixture.cs" />
@ -580,4 +583,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

@ -260,14 +260,14 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
_movieFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
{
VideoCodec = "AVC",
VideoFormat = "AVC",
AudioFormat = "DTS",
AudioLanguages = "English/Spanish",
Subtitles = "English/Spanish/Italian"
};
Subject.BuildFileName(_movie, _movieFile)
.Should().Be("South.Park.X264.DTS[EN+ES].[EN+ES+IT]");
.Should().Be("South.Park.H264.DTS[EN+ES].[EN+ES+IT]");
}
[Test]
@ -277,14 +277,52 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
_movieFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
{
VideoCodec = "AVC",
VideoFormat = "AVC",
AudioFormat = "DTS",
AudioLanguages = "English",
Subtitles = "English/Spanish/Italian"
};
Subject.BuildFileName(_movie, _movieFile)
.Should().Be("South.Park.X264.DTS.[EN+ES+IT]");
.Should().Be("South.Park.H264.DTS.[EN+ES+IT]");
}
[Test]
public void should_format_mediainfo_3d_properly()
{
_namingConfig.StandardMovieFormat = "{Movie.Title}.{MEDIAINFO.3D}.{MediaInfo.Simple}";
_movieFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
{
VideoFormat = "AVC",
VideoMultiViewCount = 2,
AudioFormat = "DTS",
AudioLanguages = "English",
Subtitles = "English/Spanish/Italian"
};
Subject.BuildFileName(_movie, _movieFile)
.Should().Be("South.Park.3D.h264.DTS");
}
[Test]
public void should_format_mediainfo_hdr_properly()
{
_namingConfig.StandardMovieFormat = "{Movie.Title}.{MEDIAINFO.HDR}.{MediaInfo.Simple}";
_movieFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
{
VideoFormat = "AVC",
VideoBitDepth = 10,
VideoColourPrimaries = "BT.2020",
VideoTransferCharacteristics = "PQ",
AudioFormat = "DTS",
AudioLanguages = "English",
Subtitles = "English/Spanish/Italian"
};
Subject.BuildFileName(_movie, _movieFile)
.Should().Be("South.Park.HDR.h264.DTS");
}
[Test]

@ -12,6 +12,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Core.Extras.Metadata.Files;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Movies;
namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
@ -32,7 +33,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
_mediaCoverService = mediaCoverService;
_diskProvider = diskProvider;
_detectNfo = detectNfo;
}
private static readonly Regex MovieImagesRegex = new Regex(@"^(?<type>poster|banner|fanart|clearart|discart|landscape|logo|backdrop|clearlogo)\.(?:png|jpg)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
@ -161,13 +162,15 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
if (movieFile.MediaInfo != null)
{
var sceneName = movieFile.GetSceneOrFileName();
var fileInfo = new XElement("fileinfo");
var streamDetails = new XElement("streamdetails");
var video = new XElement("video");
video.Add(new XElement("aspect", (float)movieFile.MediaInfo.Width / (float)movieFile.MediaInfo.Height));
video.Add(new XElement("bitrate", movieFile.MediaInfo.VideoBitrate));
video.Add(new XElement("codec", movieFile.MediaInfo.VideoCodec));
video.Add(new XElement("codec", MediaInfoFormatter.FormatVideoCodec(movieFile.MediaInfo, sceneName)));
video.Add(new XElement("framerate", movieFile.MediaInfo.VideoFps));
video.Add(new XElement("height", movieFile.MediaInfo.Height));
video.Add(new XElement("scantype", movieFile.MediaInfo.ScanType));
@ -184,7 +187,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
var audio = new XElement("audio");
audio.Add(new XElement("bitrate", movieFile.MediaInfo.AudioBitrate));
audio.Add(new XElement("channels", movieFile.MediaInfo.AudioChannels));
audio.Add(new XElement("codec", GetAudioCodec(movieFile.MediaInfo.AudioFormat)));
audio.Add(new XElement("codec", MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, sceneName)));
audio.Add(new XElement("language", movieFile.MediaInfo.AudioLanguages));
streamDetails.Add(audio);
@ -243,16 +246,6 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
return Path.ChangeExtension(movieFilePath, "nfo");
}
private string GetAudioCodec(string audioCodec)
{
if (audioCodec == "AC-3")
{
return "AC3";
}
return audioCodec;
}
private bool GetExistingWatchedStatus(Movie movie, string movieFilePath)
{
var fullPath = Path.Combine(movie.Path, GetMovieMetadataFilename(movieFilePath));

@ -0,0 +1,470 @@
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using NLog;
using NLog.Fluent;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation;
using NzbDrone.Common.Instrumentation.Extensions;
namespace NzbDrone.Core.MediaFiles.MediaInfo
{
public static class MediaInfoFormatter
{
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(MediaInfoFormatter));
public static decimal FormatAudioChannels(MediaInfoModel mediaInfo)
{
var audioChannels = FormatAudioChannelsFromAudioChannelPositions(mediaInfo);
if (audioChannels == null)
{
audioChannels = FormatAudioChannelsFromAudioChannelPositionsText(mediaInfo);
}
if (audioChannels == null)
{
audioChannels = FormatAudioChannelsFromAudioChannels(mediaInfo);
}
return audioChannels ?? 0;
}
public static string FormatAudioCodec(MediaInfoModel mediaInfo, string sceneName)
{
if (mediaInfo.AudioCodecID == null)
{
return FormatAudioCodecLegacy(mediaInfo, sceneName);
}
var audioFormat = mediaInfo.AudioFormat;
var audioCodecID = mediaInfo.AudioCodecID ?? string.Empty;
var audioProfile = mediaInfo.AudioProfile ?? string.Empty;
var audioAdditionalFeatures = mediaInfo.AudioAdditionalFeatures ?? string.Empty;
var audioCodecLibrary = mediaInfo.AudioCodecLibrary ?? string.Empty;
if (audioFormat.IsNullOrWhiteSpace())
{
return string.Empty;
}
if (audioFormat.EqualsIgnoreCase("AC-3"))
{
return "AC3";
}
if (audioFormat.EqualsIgnoreCase("E-AC-3"))
{
return "EAC3";
}
if (audioFormat.EqualsIgnoreCase("AAC"))
{
if (audioCodecID == "A_AAC/MPEG4/LC/SBR")
{
return "HE-AAC";
}
return "AAC";
}
if (audioFormat.EqualsIgnoreCase("DTS"))
{
if (audioAdditionalFeatures.StartsWithIgnoreCase("XLL"))
{
if (audioAdditionalFeatures.EndsWithIgnoreCase("X"))
{
return "DTS-X";
}
return "DTS-HD MA";
}
if (audioAdditionalFeatures.EqualsIgnoreCase("ES"))
{
return "DTS-ES";
}
if (audioAdditionalFeatures.EqualsIgnoreCase("XBR"))
{
return "DTS-HD HRA";
}
return "DTS";
}
if (audioFormat.EqualsIgnoreCase("FLAC"))
{
return "FLAC";
}
if (audioFormat.Trim().EqualsIgnoreCase("mp3"))
{
return "MP3";
}
if (audioFormat.EqualsIgnoreCase("MPEG Audio"))
{
if (mediaInfo.AudioCodecID == "55" || mediaInfo.AudioCodecID == "A_MPEG/L3" || mediaInfo.AudioProfile == "Layer 3")
{
return "MP3";
}
if (mediaInfo.AudioCodecID == "A_MPEG/L2" || mediaInfo.AudioProfile == "Layer 2")
{
return "MP2";
}
}
if (audioFormat.EqualsIgnoreCase("Opus"))
{
return "Opus";
}
if (audioFormat.EqualsIgnoreCase("PCM"))
{
return "PCM";
}
if (audioFormat.EqualsIgnoreCase("MLP FBA"))
{
if (audioAdditionalFeatures == "16-ch")
{
return "TrueHD Atmos";
}
return "TrueHD";
}
if (audioFormat.EqualsIgnoreCase("Vorbis"))
{
return "Vorbis";
}
if (audioFormat == "WMA")
{
return "WMA";
}
Logger.Debug("Unknown audio format: '{0}' in '{1}'.", string.Join(", ", audioFormat, audioCodecID, audioProfile, audioAdditionalFeatures, audioCodecLibrary), sceneName);
return audioFormat;
}
public static string FormatAudioCodecLegacy(MediaInfoModel mediaInfo, string sceneName)
{
var audioFormat = mediaInfo.AudioFormat;
if (audioFormat.IsNullOrWhiteSpace())
{
return audioFormat;
}
if (audioFormat.EqualsIgnoreCase("AC-3"))
{
return "AC3";
}
if (audioFormat.EqualsIgnoreCase("E-AC-3"))
{
return "EAC3";
}
if (audioFormat.EqualsIgnoreCase("AAC"))
{
return "AAC";
}
if (audioFormat.EqualsIgnoreCase("MPEG Audio") && mediaInfo.AudioProfile == "Layer 3")
{
return "MP3";
}
if (audioFormat.EqualsIgnoreCase("DTS"))
{
return "DTS";
}
if (audioFormat.EqualsIgnoreCase("TrueHD"))
{
return "TrueHD";
}
if (audioFormat.EqualsIgnoreCase("FLAC"))
{
return "FLAC";
}
if (audioFormat.EqualsIgnoreCase("Vorbis"))
{
return "Vorbis";
}
if (audioFormat.EqualsIgnoreCase("Opus"))
{
return "Opus";
}
return audioFormat;
}
public static string FormatVideoCodec(MediaInfoModel mediaInfo, string sceneName)
{
if (mediaInfo.VideoFormat == null)
{
return FormatVideoCodecLegacy(mediaInfo, sceneName);
}
var videoFormat = mediaInfo.VideoFormat;
var videoCodecID = mediaInfo.VideoCodecID ?? string.Empty;
var videoProfile = mediaInfo.VideoProfile ?? string.Empty;
var videoCodecLibrary = mediaInfo.VideoCodecLibrary ?? string.Empty;
var result = videoFormat;
if (videoFormat.IsNullOrWhiteSpace())
{
return result;
}
if (videoFormat == "x264")
{
return "x264";
}
if (videoFormat == "AVC" || videoFormat == "V.MPEG4/ISO/AVC")
{
if (videoCodecLibrary.StartsWithIgnoreCase("x264"))
{
return "x264";
}
return GetSceneNameMatch(sceneName, "AVC", "x264", "h264");
}
if (videoFormat == "HEVC" || videoFormat == "V_MPEGH/ISO/HEVC")
{
if (videoCodecLibrary.StartsWithIgnoreCase("x265"))
{
return "x265";
}
return GetSceneNameMatch(sceneName, "HEVC", "x265", "h265");
}
if (videoFormat == "MPEG Video")
{
if (videoCodecID == "2" || videoCodecID == "V_MPEG2")
{
return "MPEG2";
}
if (videoCodecID.IsNullOrWhiteSpace())
{
return "MPEG";
}
}
if (videoFormat == "MPEG-2 Video")
{
return "MPEG2";
}
if (videoFormat == "MPEG-4 Visual")
{
if (videoCodecID.ContainsIgnoreCase("XVID") ||
videoCodecLibrary.StartsWithIgnoreCase("XviD"))
{
return "XviD";
}
if (videoCodecID.ContainsIgnoreCase("DIV3") ||
videoCodecID.ContainsIgnoreCase("DIVX") ||
videoCodecID.ContainsIgnoreCase("DX50") ||
videoCodecLibrary.StartsWithIgnoreCase("DivX"))
{
return "DivX";
}
}
if (videoFormat == "MPEG-4 Visual" || videoFormat == "mp4v")
{
result = GetSceneNameMatch(sceneName, "XviD", "DivX", "");
if (result.IsNotNullOrWhiteSpace())
{
return result;
}
}
if (videoFormat == "VC-1")
{
return "VC1";
}
if (videoFormat.EqualsIgnoreCase("VP6") || videoFormat.EqualsIgnoreCase("VP7") ||
videoFormat.EqualsIgnoreCase("VP8") || videoFormat.EqualsIgnoreCase("VP9"))
{
return videoFormat.ToUpperInvariant();
}
if (videoFormat == "WMV2")
{
return "WMV";
}
if (videoFormat.EqualsIgnoreCase("DivX") || videoFormat.EqualsIgnoreCase("div3"))
{
return "DivX";
}
if (videoFormat.EqualsIgnoreCase("XviD"))
{
return "XviD";
}
Logger.Debug("Unknown video format: '{0}' in '{1}'.", string.Join(", ", videoFormat, videoCodecID, videoProfile, videoCodecLibrary), sceneName);
return result;
}
public static string FormatVideoCodecLegacy(MediaInfoModel mediaInfo, string sceneName)
{
var videoCodec = mediaInfo.VideoFormat;
if (videoCodec.IsNullOrWhiteSpace())
{
return videoCodec;
}
if (videoCodec == "AVC")
{
return GetSceneNameMatch(sceneName, "AVC", "h264", "x264");
}
if (videoCodec == "V_MPEGH/ISO/HEVC" || videoCodec == "HEVC")
{
return GetSceneNameMatch(sceneName, "HEVC", "h265", "x265");
}
if (videoCodec == "MPEG-2 Video")
{
return "MPEG2";
}
if (videoCodec == "MPEG-4 Visual")
{
return GetSceneNameMatch(sceneName, "DivX", "XviD");
}
if (videoCodec.StartsWithIgnoreCase("XviD"))
{
return "XviD";
}
if (videoCodec.StartsWithIgnoreCase("DivX"))
{
return "DivX";
}
if (videoCodec.EqualsIgnoreCase("VC-1"))
{
return "VC1";
}
return videoCodec;
}
private static decimal? FormatAudioChannelsFromAudioChannelPositions(MediaInfoModel mediaInfo)
{
var audioChannelPositions = mediaInfo.AudioChannelPositions;
if (audioChannelPositions.IsNullOrWhiteSpace())
{
return null;
}
try
{
Logger.Debug("Formatting audio channels using 'AudioChannelPositions', with a value of: '{0}'", audioChannelPositions);
if (audioChannelPositions.Contains("+"))
{
return audioChannelPositions.Split('+')
.Sum(s => decimal.Parse(s.Trim(), CultureInfo.InvariantCulture));
}
if (audioChannelPositions.Contains("/"))
{
return Regex.Replace(audioChannelPositions, @"^\d+\sobjects", "", RegexOptions.Compiled | RegexOptions.IgnoreCase)
.Replace("Object Based / ", "")
.Split(new string[] { " / " }, StringSplitOptions.RemoveEmptyEntries)
.FirstOrDefault()
?.Split('/')
.Sum(s => decimal.Parse(s, CultureInfo.InvariantCulture));
}
}
catch (Exception e)
{
Logger.Warn(e, "Unable to format audio channels using 'AudioChannelPositions'");
}
return null;
}
private static decimal? FormatAudioChannelsFromAudioChannelPositionsText(MediaInfoModel mediaInfo)
{
var audioChannelPositionsText = mediaInfo.AudioChannelPositionsText;
var audioChannels = mediaInfo.AudioChannels;
if (audioChannelPositionsText.IsNullOrWhiteSpace())
{
return null;
}
try
{
Logger.Debug("Formatting audio channels using 'AudioChannelPositionsText', with a value of: '{0}'", audioChannelPositionsText);
return audioChannelPositionsText.ContainsIgnoreCase("LFE") ? audioChannels - 1 + 0.1m : audioChannels;
}
catch (Exception e)
{
Logger.Warn(e, "Unable to format audio channels using 'AudioChannelPositionsText'");
}
return null;
}
private static decimal? FormatAudioChannelsFromAudioChannels(MediaInfoModel mediaInfo)
{
var audioChannels = mediaInfo.AudioChannels;
if (mediaInfo.SchemaRevision >= 3)
{
Logger.Debug("Formatting audio channels using 'AudioChannels', with a value of: '{0}'", audioChannels);
return audioChannels;
}
return null;
}
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();
}
}
}

@ -1,7 +1,6 @@
using System;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using Newtonsoft.Json;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore;
@ -10,12 +9,22 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
{
public class MediaInfoModel : IEmbeddedDocument
{
public string VideoCodec { get; set; }
public string ContainerFormat { 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 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; }
@ -28,40 +37,5 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
public string Subtitles { get; set; }
public string ScanType { get; set; }
public int SchemaRevision { get; set; }
[JsonIgnore]
public decimal FormattedAudioChannels
{
get
{
try
{
return
AudioChannelPositions.Replace("Object Based /", "").Replace(" / ", "$")
.Split('$')
.First()
.Split('/')
.Sum(s => decimal.Parse(s, CultureInfo.InvariantCulture));
}
catch
{
if (AudioChannelPositionsText.IsNullOrWhiteSpace())
{
if (SchemaRevision >= 3)
{
return AudioChannels;
}
return 0;
}
return AudioChannelPositionsText.ContainsIgnoreCase("LFE") ? AudioChannels - 1 + 0.1m : AudioChannels;
}
}
}
}
}

@ -18,8 +18,6 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
private readonly IConfigService _configService;
private readonly Logger _logger;
private const int CURRENT_MEDIA_INFO_SCHEMA_REVISION = 3;
public UpdateMediaInfoService(IDiskProvider diskProvider,
IMediaFileService mediaFileService,
IVideoFileInfoReader videoFileInfoReader,
@ -49,7 +47,6 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
if (mediaFile.MediaInfo != null)
{
mediaFile.MediaInfo.SchemaRevision = CURRENT_MEDIA_INFO_SCHEMA_REVISION;
_mediaFileService.Update(mediaFile);
_logger.Debug("Updated MediaInfo for '{0}'", path);
}
@ -65,7 +62,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
}
var allMediaFiles = _mediaFileService.GetFilesByMovie(message.Movie.Id);
var filteredMediaFiles = allMediaFiles.Where(c => c.MediaInfo == null || c.MediaInfo.SchemaRevision < CURRENT_MEDIA_INFO_SCHEMA_REVISION).ToList();
var filteredMediaFiles = allMediaFiles.Where(c => c.MediaInfo == null || c.MediaInfo.SchemaRevision < VideoFileInfoReader.MINIMUM_MEDIA_INFO_SCHEMA_REVISION).ToList();
UpdateMediaInfo(message.Movie, filteredMediaFiles);
}

@ -17,6 +17,8 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger;
public const int MINIMUM_MEDIA_INFO_SCHEMA_REVISION = 3;
public const int CURRENT_MEDIA_INFO_SCHEMA_REVISION = 4;
public VideoFileInfoReader(IDiskProvider diskProvider, Logger logger)
{
@ -90,77 +92,69 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
int audioChannels;
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");
int aBindex = aBitRate.IndexOf(" /", StringComparison.InvariantCultureIgnoreCase);
if (aBindex > 0)
{
aBitRate = aBitRate.Remove(aBindex);
}
string aBitRate = mediaInfo.Get(StreamKind.Audio, 0, "BitRate").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
int.TryParse(aBitRate, out audioBitRate);
int.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "StreamCount"), out streamCount);
string audioChannelsStr = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)");
int aCindex = audioChannelsStr.IndexOf(" /", StringComparison.InvariantCultureIgnoreCase);
if (aCindex > 0)
{
audioChannelsStr = audioChannelsStr.Remove(aCindex);
}
string audioChannelsStr = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
var audioChannelPositions = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions/String2");
var audioChannelPositionsText = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions");
string audioLanguages = mediaInfo.Get(StreamKind.General, 0, "Audio_Language_List");
string audioProfile = mediaInfo.Get(StreamKind.Audio, 0, "Format_Profile");
int aPindex = audioProfile.IndexOf(" /", StringComparison.InvariantCultureIgnoreCase);
if (aPindex > 0)
{
audioProfile = audioProfile.Remove(aPindex);
}
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();
int.TryParse(audioChannelsStr, out audioChannels);
var mediaInfoModel = new MediaInfoModel
{
VideoCodec = mediaInfo.Get(StreamKind.Video, 0, "Codec/String"),
VideoBitrate = videoBitRate,
VideoBitDepth = videoBitDepth,
Height = height,
Width = width,
AudioFormat = mediaInfo.Get(StreamKind.Audio, 0, "Format"),
AudioBitrate = audioBitRate,
RunTime = GetBestRuntime(audioRuntime, videoRuntime, generalRuntime),
AudioStreamCount = streamCount,
AudioChannels = audioChannels,
AudioChannelPositions = audioChannelPositions,
AudioChannelPositionsText = audioChannelPositionsText,
AudioProfile = audioProfile.Trim(),
VideoFps = videoFrameRate,
AudioLanguages = audioLanguages,
Subtitles = subtitles,
ScanType = scanType
};
{
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"),
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,
AudioChannels = audioChannels,
AudioChannelPositions = audioChannelPositions,
AudioChannelPositionsText = audioChannelPositionsText,
VideoFps = videoFrameRate,
AudioLanguages = audioLanguages,
Subtitles = subtitles,
ScanType = scanType,
SchemaRevision = CURRENT_MEDIA_INFO_SCHEMA_REVISION
};
return mediaInfoModel;
}
@ -175,7 +169,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to parse media info from file: " + filename);
_logger.Error(ex, "Unable to parse media info from file: {0}", filename);
}
finally
{

@ -6,6 +6,7 @@ using NzbDrone.Core.Datastore;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Movies;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.MediaFiles
{
@ -27,5 +28,20 @@ namespace NzbDrone.Core.MediaFiles
{
return string.Format("[{0}] {1}", Id, RelativePath);
}
public string GetSceneOrFileName()
{
if (SceneName.IsNotNullOrWhiteSpace())
{
return SceneName;
}
if (RelativePath.IsNotNullOrWhiteSpace())
{
return System.IO.Path.GetFileName(RelativePath);
}
return string.Empty;
}
}
}

@ -847,6 +847,7 @@
<SubType>Code</SubType>
</Compile>
<Compile Include="MediaFiles\MediaFileTableCleanupService.cs" />
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatter.cs" />
<Compile Include="MediaFiles\MediaInfo\MediaInfoLib.cs" />
<Compile Include="MediaFiles\MediaInfo\MediaInfoModel.cs" />
<Compile Include="MediaFiles\MediaInfo\UpdateMediaInfoService.cs" />
@ -1318,11 +1319,11 @@
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

@ -9,6 +9,7 @@ using NzbDrone.Common.Cache;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Movies;
@ -333,12 +334,12 @@ namespace NzbDrone.Core.Organizer
tokenHandlers["{IMDb Id}"] = m => $"{imdbId}";
}
private void AddMovieFileTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, MovieFile episodeFile)
private void AddMovieFileTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, MovieFile movieFile)
{
tokenHandlers["{Original Title}"] = m => GetOriginalTitle(episodeFile);
tokenHandlers["{Original Filename}"] = m => GetOriginalFileName(episodeFile);
tokenHandlers["{Original Title}"] = m => GetOriginalTitle(movieFile);
tokenHandlers["{Original Filename}"] = m => GetOriginalFileName(movieFile);
//tokenHandlers["{IMDb Id}"] = m =>
tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup ?? m.DefaultValue("Radarr");
tokenHandlers["{Release Group}"] = m => movieFile.ReleaseGroup ?? m.DefaultValue("Radarr");
}
private void AddQualityTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Movie movie, MovieFile movieFile)
@ -366,98 +367,22 @@ namespace NzbDrone.Core.Organizer
{
if (movieFile.MediaInfo == null) return;
string videoCodec;
switch (movieFile.MediaInfo.VideoCodec)
{
case "AVC":
if (movieFile.SceneName.IsNotNullOrWhiteSpace() && Path.GetFileNameWithoutExtension(movieFile.SceneName).Contains("h264"))
{
videoCodec = "h264";
}
else
{
videoCodec = "x264";
}
break;
case "V_MPEGH/ISO/HEVC":
if (movieFile.SceneName.IsNotNullOrWhiteSpace() && Path.GetFileNameWithoutExtension(movieFile.SceneName).Contains("h265"))
{
videoCodec = "h265";
}
else
{
videoCodec = "x265";
}
break;
case "MPEG-2 Video":
videoCodec = "MPEG2";
break;
var sceneName = movieFile.GetSceneOrFileName();
default:
videoCodec = movieFile.MediaInfo.VideoCodec;
break;
}
var videoCodec = MediaInfoFormatter.FormatVideoCodec(movieFile.MediaInfo, sceneName);
var audioCodec = MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, sceneName);
var audioChannels = MediaInfoFormatter.FormatAudioChannels(movieFile.MediaInfo);
string audioCodec;
switch (movieFile.MediaInfo.AudioFormat)
// Workaround until https://github.com/MediaArea/MediaInfo/issues/299 is fixed and release
if (audioCodec.EqualsIgnoreCase("DTS-X"))
{
case "AC-3":
audioCodec = "AC3";
break;
case "E-AC-3":
audioCodec = "EAC3";
break;
case "Atmos / TrueHD":
audioCodec = "Atmos TrueHD";
break;
case "MPEG Audio":
if (movieFile.MediaInfo.AudioProfile == "Layer 3")
{
audioCodec = "MP3";
}
else
{
audioCodec = movieFile.MediaInfo.AudioFormat;
}
break;
case "DTS":
if (movieFile.MediaInfo.AudioProfile == "ES" || movieFile.MediaInfo.AudioProfile == "ES Discrete" || movieFile.MediaInfo.AudioProfile == "ES Matrix")
{
audioCodec = "DTS-ES";
}
else if (movieFile.MediaInfo.AudioProfile == "MA")
{
audioCodec = "DTS-HD MA";
}
else if (movieFile.MediaInfo.AudioProfile == "HRA")
{
audioCodec = "DTS-HD HRA";
}
else if (movieFile.MediaInfo.AudioProfile == "X")
{
audioCodec = "DTS-X";
}
else
{
audioCodec = movieFile.MediaInfo.AudioFormat;
}
break;
default:
audioCodec = movieFile.MediaInfo.AudioFormat;
break;
audioChannels = audioChannels - 1 + 0.1m;
}
var mediaInfoAudioLanguages = GetLanguagesToken(movieFile.MediaInfo.AudioLanguages);
if (!mediaInfoAudioLanguages.IsNullOrWhiteSpace())
{
mediaInfoAudioLanguages = string.Format("[{0}]", mediaInfoAudioLanguages);
mediaInfoAudioLanguages = $"[{mediaInfoAudioLanguages}]";
}
var mediaInfoAudioLanguagesAll = mediaInfoAudioLanguages;
if (mediaInfoAudioLanguages == "[EN]")
@ -465,17 +390,32 @@ namespace NzbDrone.Core.Organizer
mediaInfoAudioLanguages = string.Empty;
}
var mediaInfoSubtitleLanguages = GetLanguagesToken(movieFile.MediaInfo.Subtitles);
if (!mediaInfoSubtitleLanguages.IsNullOrWhiteSpace())
{
mediaInfoSubtitleLanguages = string.Format("[{0}]", mediaInfoSubtitleLanguages);
mediaInfoSubtitleLanguages = $"[{mediaInfoSubtitleLanguages}]";
}
var videoBitDepth = movieFile.MediaInfo.VideoBitDepth > 0 ? movieFile.MediaInfo.VideoBitDepth.ToString() : string.Empty;
var audioChannels = movieFile.MediaInfo.FormattedAudioChannels > 0 ?
movieFile.MediaInfo.FormattedAudioChannels.ToString("F1", CultureInfo.InvariantCulture) :
string.Empty;
var audioChannelsFormatted = audioChannels > 0 ?
audioChannels.ToString("F1", CultureInfo.InvariantCulture) :
string.Empty;
var mediaInfo3D = movieFile.MediaInfo.VideoMultiViewCount > 1 ? "3D" : string.Empty;
var videoColourPrimaries = movieFile.MediaInfo.VideoColourPrimaries ?? string.Empty;
var videoTransferCharacteristics = movieFile.MediaInfo.VideoTransferCharacteristics ?? string.Empty;
var mediaInfoHDR = string.Empty;
if (movieFile.MediaInfo.VideoBitDepth >= 10 && !videoColourPrimaries.IsNullOrWhiteSpace() && !videoTransferCharacteristics.IsNullOrWhiteSpace())
{
string[] validTransferFunctions = new string[] { "PQ", "HLG" };
if (videoColourPrimaries.EqualsIgnoreCase("BT.2020") && validTransferFunctions.Any(videoTransferCharacteristics.Contains))
{
mediaInfoHDR = "HDR";
}
}
tokenHandlers["{MediaInfo Video}"] = m => videoCodec;
tokenHandlers["{MediaInfo VideoCodec}"] = m => videoCodec;
@ -483,14 +423,17 @@ namespace NzbDrone.Core.Organizer
tokenHandlers["{MediaInfo Audio}"] = m => audioCodec;
tokenHandlers["{MediaInfo AudioCodec}"] = m => audioCodec;
tokenHandlers["{MediaInfo AudioChannels}"] = m => audioChannels;
tokenHandlers["{MediaInfo Simple}"] = m => string.Format("{0} {1}", videoCodec, audioCodec);
tokenHandlers["{MediaInfo Full}"] = m => string.Format("{0} {1}{2} {3}", videoCodec, audioCodec, mediaInfoAudioLanguages, mediaInfoSubtitleLanguages);
tokenHandlers["{MediaInfo AudioChannels}"] = m => audioChannelsFormatted;
tokenHandlers["{MediaInfo AudioLanguages}"] = m => mediaInfoAudioLanguages;
tokenHandlers["{MediaInfo AudioLanguagesAll}"] = m => mediaInfoAudioLanguagesAll;
tokenHandlers["{MediaInfo SubtitleLanguages}"] = m => mediaInfoSubtitleLanguages;
tokenHandlers["{MediaInfo 3D}"] = m => mediaInfo3D;
tokenHandlers["{MediaInfo HDR}"] = m => mediaInfoHDR;
tokenHandlers["{MediaInfo Simple}"] = m => $"{videoCodec} {audioCodec}";
tokenHandlers["{MediaInfo Full}"] = m => $"{videoCodec} {audioCodec}{mediaInfoAudioLanguages} {mediaInfoSubtitleLanguages}";
}
private string GetLanguagesToken(string mediaInfoLanguages)
@ -610,24 +553,24 @@ namespace NzbDrone.Core.Organizer
return string.Empty;
}
private string GetOriginalTitle(MovieFile episodeFile)
private string GetOriginalTitle(MovieFile movieFile)
{
if (episodeFile.SceneName.IsNullOrWhiteSpace())
if (movieFile.SceneName.IsNullOrWhiteSpace())
{
return GetOriginalFileName(episodeFile);
return GetOriginalFileName(movieFile);
}
return episodeFile.SceneName;
return movieFile.SceneName;
}
private string GetOriginalFileName(MovieFile episodeFile)
private string GetOriginalFileName(MovieFile movieFile)
{
if (episodeFile.RelativePath.IsNullOrWhiteSpace())
if (movieFile.RelativePath.IsNullOrWhiteSpace())
{
return Path.GetFileNameWithoutExtension(episodeFile.Path);
return Path.GetFileNameWithoutExtension(movieFile.Path);
}
return Path.GetFileNameWithoutExtension(episodeFile.RelativePath);
return Path.GetFileNameWithoutExtension(movieFile.RelativePath);
}
}

@ -26,8 +26,11 @@ namespace NzbDrone.Core.Organizer
var mediaInfo = new MediaInfoModel()
{
VideoCodec = "AVC",
VideoBitDepth = 8,
VideoFormat = "AVC",
VideoBitDepth = 10,
VideoMultiViewCount = 2,
VideoColourPrimaries = "BT.2020",
VideoTransferCharacteristics = "PQ",
AudioFormat = "DTS",
AudioChannels = 6,
AudioChannelPositions = "3/2/0.1",
@ -37,8 +40,11 @@ namespace NzbDrone.Core.Organizer
var mediaInfoAnime = new MediaInfoModel()
{
VideoCodec = "AVC",
VideoBitDepth = 8,
VideoFormat = "AVC",
VideoBitDepth = 10,
VideoMultiViewCount = 2,
VideoColourPrimaries = "BT.2020",
VideoTransferCharacteristics = "PQ",
AudioFormat = "DTS",
AudioChannels = 6,
AudioChannelPositions = "3/2/0.1",

@ -12,7 +12,7 @@ module.exports = NzbDroneCell.extend({
if (runtime) {
runtime = runtime.split(".")[0];
}
var video = "{0} ({1}x{2}) ({3})".format(info.videoCodec, info.width, info.height, runtime);
var video = "{0} ({1}x{2}) ({3})".format(info.videoFormat, info.width, info.height, runtime);
var audio = "{0} ({1})".format(info.audioFormat, info.audioLanguages);
this.$el.html(video + " " + audio);
}

Loading…
Cancel
Save