mirror of
https://github.com/Radarr/Radarr.git
synced 2024-09-11 12:02:35 +02:00
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
This commit is contained in:
parent
4009852c35
commit
97f111bec8
Binary file not shown.
Binary file not shown.
@ -78,6 +78,21 @@ public static bool IsNotNullOrWhiteSpace(this string text)
|
||||
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 @@ public static string FromOctalString(this string octalValue)
|
||||
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 @@ public void should_skip_up_to_date_media_info()
|
||||
.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 @@ public void should_skip_up_to_date_media_info()
|
||||
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 @@ public void should_update_outdated_media_info()
|
||||
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 @@ public void should_ignore_missing_files()
|
||||
|
||||
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 @@ public void should_continue_after_failure()
|
||||
|
||||
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 @@ public void get_runtime()
|
||||
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 @@ public void get_info()
|
||||
|
||||
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 @@ public void get_info_unicode()
|
||||
|
||||
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 @@ public void should_dispose_file_after_scanning_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 @@ public void should_format_mediainfo_properly()
|
||||
|
||||
_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 @@ public void should_exclude_english_in_mediainfo_audio_language()
|
||||
|
||||
_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.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 @@ public XbmcMetadata(IDetectXbmcNfo detectNfo,
|
||||
_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 @@ public override MetadataFileResult MovieMetadata(Movie movie, MovieFile movieFil
|
||||
|
||||
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 @@ public override MetadataFileResult MovieMetadata(Movie movie, MovieFile movieFil
|
||||
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 @@ private string GetMovieMetadataFilename(string movieFilePath)
|
||||
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));
|
||||
|
470
src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs
Normal file
470
src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs
Normal file
@ -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 @@ public class MediaInfoModel : IEmbeddedDocument
|
||||
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 @@ public class UpdateMediaInfoService : IHandle<MovieScannedEvent>
|
||||
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 @@ private void UpdateMediaInfo(Movie movie, List<MovieFile> mediaFiles)
|
||||
|
||||
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 @@ public void Handle(MovieScannedEvent message)
|
||||
}
|
||||
|
||||
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 @@ public class VideoFileInfoReader : IVideoFileInfoReader
|
||||
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 @@ public MediaInfoModel GetMediaInfo(string filename)
|
||||
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 @@ public MediaInfoModel GetMediaInfo(string filename)
|
||||
}
|
||||
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.Qualities;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles
|
||||
{
|
||||
@ -27,5 +28,20 @@ public override string ToString()
|
||||
{
|
||||
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.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 @@ private void AddImdbIdTokens(Dictionary<string, Func<TokenMatch, string>> tokenH
|
||||
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 @@ private void AddMediaInfoTokens(Dictionary<string, Func<TokenMatch, string>> tok
|
||||
{
|
||||
if (movieFile.MediaInfo == null) return;
|
||||
|
||||
string videoCodec;
|
||||
switch (movieFile.MediaInfo.VideoCodec)
|
||||
var sceneName = movieFile.GetSceneOrFileName();
|
||||
|
||||
var videoCodec = MediaInfoFormatter.FormatVideoCodec(movieFile.MediaInfo, sceneName);
|
||||
var audioCodec = MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, sceneName);
|
||||
var audioChannels = MediaInfoFormatter.FormatAudioChannels(movieFile.MediaInfo);
|
||||
|
||||
// Workaround until https://github.com/MediaArea/MediaInfo/issues/299 is fixed and release
|
||||
if (audioCodec.EqualsIgnoreCase("DTS-X"))
|
||||
{
|
||||
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;
|
||||
|
||||
default:
|
||||
videoCodec = movieFile.MediaInfo.VideoCodec;
|
||||
break;
|
||||
}
|
||||
|
||||
string audioCodec;
|
||||
switch (movieFile.MediaInfo.AudioFormat)
|
||||
{
|
||||
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 @@ private void AddMediaInfoTokens(Dictionary<string, Func<TokenMatch, string>> tok
|
||||
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 @@ private void AddMediaInfoTokens(Dictionary<string, Func<TokenMatch, string>> tok
|
||||
|
||||
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 @@ private string GetQualityReal(Movie movie, QualityModel quality)
|
||||
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 @@ public FileNameSampleService(IBuildFileNames buildFileNames)
|
||||
|
||||
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 @@ public FileNameSampleService(IBuildFileNames buildFileNames)
|
||||
|
||||
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…
Reference in New Issue
Block a user