Updated MediaInfo schema and revised logic that Formats it. Also added logic to log events to Sentry.
This commit is contained in:
parent
94f2473fbb
commit
27dca830cc
|
@ -78,6 +78,16 @@ namespace NzbDrone.Common.Extensions
|
||||||
return !string.IsNullOrWhiteSpace(text);
|
return !string.IsNullOrWhiteSpace(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool StartsWithIgnoreCase(this string text, string startsWith)
|
||||||
|
{
|
||||||
|
return text.StartsWith(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)
|
public static bool ContainsIgnoreCase(this string text, string contains)
|
||||||
{
|
{
|
||||||
return text.IndexOf(contains, StringComparison.InvariantCultureIgnoreCase) > -1;
|
return text.IndexOf(contains, StringComparison.InvariantCultureIgnoreCase) > -1;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using NLog.Fluent;
|
||||||
|
|
||||||
namespace NzbDrone.Common.Instrumentation.Extensions
|
namespace NzbDrone.Common.Instrumentation.Extensions
|
||||||
{
|
{
|
|
@ -0,0 +1,58 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NLog;
|
||||||
|
using NLog.Fluent;
|
||||||
|
|
||||||
|
namespace NzbDrone.Common.Instrumentation.Extensions
|
||||||
|
{
|
||||||
|
public static class SentryLoggerExtensions
|
||||||
|
{
|
||||||
|
public static readonly Logger SentryLogger = LogManager.GetLogger("Sentry");
|
||||||
|
|
||||||
|
public static LogBuilder SentryFingerprint(this LogBuilder logBuilder, params string[] fingerprint)
|
||||||
|
{
|
||||||
|
return logBuilder.Property("Sentry", fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LogBuilder WriteSentryDebug(this LogBuilder logBuilder, params string[] fingerprint)
|
||||||
|
{
|
||||||
|
return LogSentryMessage(logBuilder, LogLevel.Debug, fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LogBuilder WriteSentryInfo(this LogBuilder logBuilder, params string[] fingerprint)
|
||||||
|
{
|
||||||
|
return LogSentryMessage(logBuilder, LogLevel.Info, fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LogBuilder WriteSentryWarn(this LogBuilder logBuilder, params string[] fingerprint)
|
||||||
|
{
|
||||||
|
return LogSentryMessage(logBuilder, LogLevel.Warn, fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LogBuilder WriteSentryError(this LogBuilder logBuilder, params string[] fingerprint)
|
||||||
|
{
|
||||||
|
return LogSentryMessage(logBuilder, LogLevel.Error, fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LogBuilder LogSentryMessage(LogBuilder logBuilder, LogLevel level, string[] fingerprint)
|
||||||
|
{
|
||||||
|
SentryLogger.Log(level)
|
||||||
|
.CopyLogEvent(logBuilder.LogEventInfo)
|
||||||
|
.SentryFingerprint(fingerprint)
|
||||||
|
.Write();
|
||||||
|
|
||||||
|
return logBuilder.Property("Sentry", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LogBuilder CopyLogEvent(this LogBuilder logBuilder, LogEventInfo logEvent)
|
||||||
|
{
|
||||||
|
return logBuilder.LoggerName(logEvent.LoggerName)
|
||||||
|
.TimeStamp(logEvent.TimeStamp)
|
||||||
|
.Message(logEvent.Message, logEvent.Parameters)
|
||||||
|
.Properties((Dictionary<object, object>)logEvent.Properties)
|
||||||
|
.Exception(logEvent.Exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -109,9 +109,13 @@ namespace NzbDrone.Common.Instrumentation
|
||||||
Layout = "${message}"
|
Layout = "${message}"
|
||||||
};
|
};
|
||||||
|
|
||||||
var loggingRule = new LoggingRule("*", updateClient ? LogLevel.Trace : LogLevel.Error, target);
|
var loggingRule = new LoggingRule("*", updateClient ? LogLevel.Trace : LogLevel.Warn, target);
|
||||||
LogManager.Configuration.AddTarget("sentryTarget", target);
|
LogManager.Configuration.AddTarget("sentryTarget", target);
|
||||||
LogManager.Configuration.LoggingRules.Add(loggingRule);
|
LogManager.Configuration.LoggingRules.Add(loggingRule);
|
||||||
|
|
||||||
|
// Events logged to Sentry go only to Sentry.
|
||||||
|
var loggingRuleSentry = new LoggingRule("Sentry", LogLevel.Debug, target) { Final = true };
|
||||||
|
LogManager.Configuration.LoggingRules.Insert(0, loggingRuleSentry);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RegisterDebugger()
|
private static void RegisterDebugger()
|
||||||
|
|
|
@ -71,6 +71,11 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
||||||
|
|
||||||
private static List<string> GetFingerPrint(LogEventInfo logEvent)
|
private static List<string> GetFingerPrint(LogEventInfo logEvent)
|
||||||
{
|
{
|
||||||
|
if (logEvent.Properties.ContainsKey("Sentry"))
|
||||||
|
{
|
||||||
|
return ((string[])logEvent.Properties["Sentry"]).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
var fingerPrint = new List<string>
|
var fingerPrint = new List<string>
|
||||||
{
|
{
|
||||||
logEvent.Level.Ordinal.ToString(),
|
logEvent.Level.Ordinal.ToString(),
|
||||||
|
@ -94,13 +99,33 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
||||||
return fingerPrint;
|
return fingerPrint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsSentryMessage(LogEventInfo logEvent)
|
||||||
|
{
|
||||||
|
if (logEvent.Properties.ContainsKey("Sentry"))
|
||||||
|
{
|
||||||
|
return logEvent.Properties["Sentry"] != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logEvent.Level >= LogLevel.Error && logEvent.Exception != null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected override void Write(LogEventInfo logEvent)
|
protected override void Write(LogEventInfo logEvent)
|
||||||
{
|
{
|
||||||
|
if (_unauthorized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// don't report non-critical events without exceptions
|
// don't report non-critical events without exceptions
|
||||||
if (logEvent.Exception == null || _unauthorized)
|
if (!IsSentryMessage(logEvent))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -112,6 +137,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
||||||
}
|
}
|
||||||
|
|
||||||
var extras = logEvent.Properties.ToDictionary(x => x.Key.ToString(), x => x.Value.ToString());
|
var extras = logEvent.Properties.ToDictionary(x => x.Key.ToString(), x => x.Value.ToString());
|
||||||
|
extras.Remove("Sentry");
|
||||||
_client.Logger = logEvent.LoggerName;
|
_client.Logger = logEvent.LoggerName;
|
||||||
|
|
||||||
var sentryMessage = new SentryMessage(logEvent.Message, logEvent.Parameters);
|
var sentryMessage = new SentryMessage(logEvent.Message, logEvent.Parameters);
|
||||||
|
@ -134,11 +160,16 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
||||||
sentryEvent.Fingerprint.Add(logEvent.Exception.GetType().FullName);
|
sentryEvent.Fingerprint.Add(logEvent.Exception.GetType().FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (logEvent.Properties.ContainsKey("Sentry"))
|
||||||
|
{
|
||||||
|
sentryEvent.Fingerprint.Clear();
|
||||||
|
Array.ForEach((string[])logEvent.Properties["Sentry"], sentryEvent.Fingerprint.Add);
|
||||||
|
}
|
||||||
|
|
||||||
var osName = Environment.GetEnvironmentVariable("OS_NAME");
|
var osName = Environment.GetEnvironmentVariable("OS_NAME");
|
||||||
var osVersion = Environment.GetEnvironmentVariable("OS_VERSION");
|
var osVersion = Environment.GetEnvironmentVariable("OS_VERSION");
|
||||||
var runTimeVersion = Environment.GetEnvironmentVariable("RUNTIME_VERSION");
|
var runTimeVersion = Environment.GetEnvironmentVariable("RUNTIME_VERSION");
|
||||||
|
|
||||||
|
|
||||||
sentryEvent.Tags.Add("os_name", osName);
|
sentryEvent.Tags.Add("os_name", osName);
|
||||||
sentryEvent.Tags.Add("os_version", $"{osName} {osVersion}");
|
sentryEvent.Tags.Add("os_version", $"{osName} {osVersion}");
|
||||||
sentryEvent.Tags.Add("runtime_version", $"{PlatformInfo.PlatformName} {runTimeVersion}");
|
sentryEvent.Tags.Add("runtime_version", $"{PlatformInfo.PlatformName} {runTimeVersion}");
|
||||||
|
|
|
@ -177,7 +177,8 @@
|
||||||
<Compile Include="Http\UserAgentBuilder.cs" />
|
<Compile Include="Http\UserAgentBuilder.cs" />
|
||||||
<Compile Include="Instrumentation\CleanseLogMessage.cs" />
|
<Compile Include="Instrumentation\CleanseLogMessage.cs" />
|
||||||
<Compile Include="Instrumentation\CleansingJsonVisitor.cs" />
|
<Compile Include="Instrumentation\CleansingJsonVisitor.cs" />
|
||||||
<Compile Include="Instrumentation\Extensions\LoggerProgressExtensions.cs" />
|
<Compile Include="Instrumentation\Extensions\LoggerExtensions.cs" />
|
||||||
|
<Compile Include="Instrumentation\Extensions\SentryLoggerExtensions.cs" />
|
||||||
<Compile Include="Instrumentation\GlobalExceptionHandlers.cs" />
|
<Compile Include="Instrumentation\GlobalExceptionHandlers.cs" />
|
||||||
<Compile Include="Instrumentation\LogEventExtensions.cs" />
|
<Compile Include="Instrumentation\LogEventExtensions.cs" />
|
||||||
<Compile Include="Instrumentation\NzbDroneFileTarget.cs" />
|
<Compile Include="Instrumentation\NzbDroneFileTarget.cs" />
|
||||||
|
|
|
@ -41,11 +41,12 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
|
||||||
{
|
{
|
||||||
var mediaInfoModel = new MediaInfoModel
|
var mediaInfoModel = new MediaInfoModel
|
||||||
{
|
{
|
||||||
AudioFormat = "Other Audio Format"
|
AudioFormat = "Other Audio Format",
|
||||||
|
AudioCodecID = "Other Audio Codec"
|
||||||
};
|
};
|
||||||
|
|
||||||
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel, sceneName).Should().Be(mediaInfoModel.AudioFormat);
|
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel, sceneName).Should().Be(mediaInfoModel.AudioFormat);
|
||||||
ExceptionVerification.ExpectedErrors(1);
|
ExceptionVerification.ExpectedWarns(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,15 +26,15 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_VideoCodec_by_default()
|
public void should_return_VideoFormat_by_default()
|
||||||
{
|
{
|
||||||
var mediaInfoModel = new MediaInfoModel
|
var mediaInfoModel = new MediaInfoModel
|
||||||
{
|
{
|
||||||
VideoCodec = "VideoCodec"
|
VideoFormat = "VideoCodec"
|
||||||
};
|
};
|
||||||
|
|
||||||
MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, null).Should().Be(mediaInfoModel.VideoCodec);
|
MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, null).Should().Be(mediaInfoModel.VideoFormat);
|
||||||
ExceptionVerification.ExpectedErrors(1);
|
ExceptionVerification.ExpectedWarns(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,33 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
||||||
.All()
|
.All()
|
||||||
.With(v => v.RelativePath = "media.mkv")
|
.With(v => v.RelativePath = "media.mkv")
|
||||||
.TheFirst(1)
|
.TheFirst(1)
|
||||||
.With(v => v.MediaInfo = new MediaInfoModel { SchemaRevision = 3 })
|
.With(v => v.MediaInfo = new MediaInfoModel { SchemaRevision = UpdateMediaInfoService.CURRENT_MEDIA_INFO_SCHEMA_REVISION })
|
||||||
|
.BuildList();
|
||||||
|
|
||||||
|
Mocker.GetMock<IMediaFileService>()
|
||||||
|
.Setup(v => v.GetFilesBySeries(1))
|
||||||
|
.Returns(episodeFiles);
|
||||||
|
|
||||||
|
GivenFileExists();
|
||||||
|
GivenSuccessfulScan();
|
||||||
|
|
||||||
|
Subject.Handle(new SeriesScannedEvent(_series));
|
||||||
|
|
||||||
|
Mocker.GetMock<IVideoFileInfoReader>()
|
||||||
|
.Verify(v => v.GetMediaInfo(Path.Combine(_series.Path, "media.mkv")), Times.Exactly(2));
|
||||||
|
|
||||||
|
Mocker.GetMock<IMediaFileService>()
|
||||||
|
.Verify(v => v.Update(It.IsAny<EpisodeFile>()), Times.Exactly(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_skip_not_yet_date_media_info()
|
||||||
|
{
|
||||||
|
var episodeFiles = Builder<EpisodeFile>.CreateListOfSize(3)
|
||||||
|
.All()
|
||||||
|
.With(v => v.RelativePath = "media.mkv")
|
||||||
|
.TheFirst(1)
|
||||||
|
.With(v => v.MediaInfo = new MediaInfoModel { SchemaRevision = UpdateMediaInfoService.MINIMUM_MEDIA_INFO_SCHEMA_REVISION })
|
||||||
.BuildList();
|
.BuildList();
|
||||||
|
|
||||||
Mocker.GetMock<IMediaFileService>()
|
Mocker.GetMock<IMediaFileService>()
|
||||||
|
|
|
@ -3,8 +3,10 @@ using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using NLog.Fluent;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Common.Instrumentation;
|
using NzbDrone.Common.Instrumentation;
|
||||||
|
using NzbDrone.Common.Instrumentation.Extensions;
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles.MediaInfo
|
namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
{
|
{
|
||||||
|
@ -41,6 +43,74 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string FormatAudioCodec(MediaInfoModel mediaInfo, string sceneName)
|
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 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"))
|
||||||
|
{
|
||||||
|
return "DTS";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioFormat.EqualsIgnoreCase("FLAC"))
|
||||||
|
{
|
||||||
|
return "FLAC";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioFormat.EqualsIgnoreCase("MPEG Audio"))
|
||||||
|
{
|
||||||
|
if (mediaInfo.AudioCodecID == "55" || mediaInfo.AudioCodecID == "A_MPEG/L3" || mediaInfo.AudioProfile == "Layer 3")
|
||||||
|
{
|
||||||
|
return "MP3";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaInfo.AudioProfile == "Layer 2")
|
||||||
|
{
|
||||||
|
return "MP2";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Debug()
|
||||||
|
.Message("Unknown audio format: '{0}' in '{1}'.", string.Join(", ", audioFormat, audioCodecID, audioProfile, audioCodecLibrary), sceneName)
|
||||||
|
.WriteSentryWarn("UnknownAudioFormat", mediaInfo.ContainerFormat, audioFormat, audioCodecID)
|
||||||
|
.Write();
|
||||||
|
|
||||||
|
return audioFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string FormatAudioCodecLegacy(MediaInfoModel mediaInfo, string sceneName)
|
||||||
{
|
{
|
||||||
var audioFormat = mediaInfo.AudioFormat;
|
var audioFormat = mediaInfo.AudioFormat;
|
||||||
|
|
||||||
|
@ -49,51 +119,120 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
return audioFormat;
|
return audioFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioFormat == "AC-3")
|
if (audioFormat.EqualsIgnoreCase("AC-3"))
|
||||||
{
|
{
|
||||||
return "AC3";
|
return "AC3";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioFormat == "E-AC-3")
|
if (audioFormat.EqualsIgnoreCase("E-AC-3"))
|
||||||
{
|
{
|
||||||
return "EAC3";
|
return "EAC3";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioFormat == "AAC")
|
if (audioFormat.EqualsIgnoreCase("AAC"))
|
||||||
{
|
{
|
||||||
return "AAC";
|
return "AAC";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioFormat == "MPEG Audio")
|
if (audioFormat.EqualsIgnoreCase("MPEG Audio") && mediaInfo.AudioProfile == "Layer 3")
|
||||||
{
|
{
|
||||||
return mediaInfo.AudioProfile == "Layer 3" ? "MP3" : audioFormat;
|
return "MP3";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioFormat == "DTS")
|
if (audioFormat.EqualsIgnoreCase("DTS"))
|
||||||
{
|
{
|
||||||
return "DTS";
|
return "DTS";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioFormat.Equals("FLAC", StringComparison.OrdinalIgnoreCase))
|
if (audioFormat.EqualsIgnoreCase("TrueHD"))
|
||||||
|
{
|
||||||
|
return "TrueHD";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioFormat.EqualsIgnoreCase("FLAC"))
|
||||||
{
|
{
|
||||||
return "FLAC";
|
return "FLAC";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioFormat.Equals("Vorbis", StringComparison.OrdinalIgnoreCase))
|
if (audioFormat.EqualsIgnoreCase("Vorbis"))
|
||||||
{
|
{
|
||||||
return "Vorbis";
|
return "Vorbis";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioFormat.Equals("Opus", StringComparison.OrdinalIgnoreCase))
|
if (audioFormat.EqualsIgnoreCase("Opus"))
|
||||||
{
|
{
|
||||||
return "Opus";
|
return "Opus";
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Error(new UnknownCodecException(audioFormat, sceneName), "Unknown audio format: {0} in '{1}'. Please notify Sonarr developers.", audioFormat, sceneName);
|
|
||||||
return audioFormat;
|
return audioFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string FormatVideoCodec(MediaInfoModel mediaInfo, string sceneName)
|
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;
|
||||||
|
|
||||||
|
if (videoFormat.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return videoFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoFormat == "AVC")
|
||||||
|
{
|
||||||
|
if (videoCodecLibrary.StartsWithIgnoreCase("x264"))
|
||||||
|
{
|
||||||
|
return "x264";
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetSceneNameMatch(sceneName, "AVC", "h264");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoFormat == "HEVC")
|
||||||
|
{
|
||||||
|
if (videoCodecLibrary.StartsWithIgnoreCase("x265"))
|
||||||
|
{
|
||||||
|
return "x265";
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetSceneNameMatch(sceneName, "HEVC", "h265");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoFormat == "MPEG-2 Video")
|
||||||
|
{
|
||||||
|
return "MPEG2";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoFormat == "MPEG-4 Visual")
|
||||||
|
{
|
||||||
|
if (videoCodecID.ContainsIgnoreCase("XVID"))
|
||||||
|
{
|
||||||
|
return "XviD";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoCodecID.ContainsIgnoreCase("DIV3") ||
|
||||||
|
videoCodecID.ContainsIgnoreCase("DIVX") ||
|
||||||
|
videoCodecID.ContainsIgnoreCase("DX50"))
|
||||||
|
{
|
||||||
|
return "DivX";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Debug()
|
||||||
|
.Message("Unknown video format: '{0}' in '{1}'.", string.Join(", ", videoFormat, videoCodecID, videoProfile, videoCodecLibrary), sceneName)
|
||||||
|
.WriteSentryWarn("UnknownVideoFormat", mediaInfo.ContainerFormat, videoFormat, videoCodecID)
|
||||||
|
.Write();
|
||||||
|
|
||||||
|
return videoFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string FormatVideoCodecLegacy(MediaInfoModel mediaInfo, string sceneName)
|
||||||
{
|
{
|
||||||
var videoCodec = mediaInfo.VideoCodec;
|
var videoCodec = mediaInfo.VideoCodec;
|
||||||
|
|
||||||
|
@ -104,16 +243,12 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
|
|
||||||
if (videoCodec == "AVC")
|
if (videoCodec == "AVC")
|
||||||
{
|
{
|
||||||
return sceneName.IsNotNullOrWhiteSpace() && Path.GetFileNameWithoutExtension(sceneName).Contains("h264")
|
return GetSceneNameMatch(sceneName, "AVC", "h264", "x264");
|
||||||
? "h264"
|
|
||||||
: "x264";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoCodec == "V_MPEGH/ISO/HEVC" || videoCodec == "HEVC")
|
if (videoCodec == "V_MPEGH/ISO/HEVC" || videoCodec == "HEVC")
|
||||||
{
|
{
|
||||||
return sceneName.IsNotNullOrWhiteSpace() && Path.GetFileNameWithoutExtension(sceneName).ContainsIgnoreCase("h265")
|
return GetSceneNameMatch(sceneName, "HEVC", "h265", "x265");
|
||||||
? "h265"
|
|
||||||
: "x265";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoCodec == "MPEG-2 Video")
|
if (videoCodec == "MPEG-2 Video")
|
||||||
|
@ -123,28 +258,41 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
|
|
||||||
if (videoCodec == "MPEG-4 Visual")
|
if (videoCodec == "MPEG-4 Visual")
|
||||||
{
|
{
|
||||||
return sceneName.IsNotNullOrWhiteSpace() && Path.GetFileNameWithoutExtension(sceneName).ContainsIgnoreCase("DivX")
|
return GetSceneNameMatch(sceneName, "DivX", "XviD");
|
||||||
? "DivX"
|
|
||||||
: "XviD";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoCodec.StartsWith("XviD", StringComparison.OrdinalIgnoreCase))
|
if (videoCodec.StartsWithIgnoreCase("XviD"))
|
||||||
{
|
{
|
||||||
return "XviD";
|
return "XviD";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoCodec.StartsWith("DivX", StringComparison.OrdinalIgnoreCase))
|
if (videoCodec.StartsWithIgnoreCase("DivX"))
|
||||||
{
|
{
|
||||||
return "DivX";
|
return "DivX";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoCodec.Equals("VC-1", StringComparison.OrdinalIgnoreCase))
|
if (videoCodec.EqualsIgnoreCase("VC-1"))
|
||||||
{
|
{
|
||||||
return "VC1";
|
return "VC1";
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Error(new UnknownCodecException(videoCodec, sceneName), "Unknown video codec: {0} in '{1}'. Please notify Sonarr developers.", videoCodec, sceneName);
|
|
||||||
return videoCodec;
|
return videoCodec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GetSceneNameMatch(string sceneName, params string[] tokens)
|
||||||
|
{
|
||||||
|
sceneName = sceneName.IsNotNullOrWhiteSpace() ? Path.GetFileNameWithoutExtension(sceneName) : string.Empty;
|
||||||
|
|
||||||
|
foreach (var token in tokens)
|
||||||
|
{
|
||||||
|
if (sceneName.ContainsIgnoreCase(token))
|
||||||
|
{
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last token is the default.
|
||||||
|
return tokens.Last();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,20 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
{
|
{
|
||||||
public class MediaInfoModel : IEmbeddedDocument
|
public class MediaInfoModel : IEmbeddedDocument
|
||||||
{
|
{
|
||||||
|
public string ContainerFormat { get; set; }
|
||||||
|
// Deprecated according to MediaInfo
|
||||||
public string VideoCodec { get; set; }
|
public string VideoCodec { get; set; }
|
||||||
|
public string VideoFormat { get; set; }
|
||||||
|
public string VideoCodecID { get; set; }
|
||||||
|
public string VideoProfile { get; set; }
|
||||||
|
public string VideoCodecLibrary { get; set; }
|
||||||
public int VideoBitrate { get; set; }
|
public int VideoBitrate { get; set; }
|
||||||
public int VideoBitDepth { get; set; }
|
public int VideoBitDepth { get; set; }
|
||||||
public int Width { get; set; }
|
public int Width { get; set; }
|
||||||
public int Height { get; set; }
|
public int Height { get; set; }
|
||||||
public string AudioFormat { get; set; }
|
public string AudioFormat { get; set; }
|
||||||
|
public string AudioCodecID { get; set; }
|
||||||
|
public string AudioCodecLibrary { get; set; }
|
||||||
public int AudioBitrate { get; set; }
|
public int AudioBitrate { get; set; }
|
||||||
public TimeSpan RunTime { get; set; }
|
public TimeSpan RunTime { get; set; }
|
||||||
public int AudioStreamCount { get; set; }
|
public int AudioStreamCount { get; set; }
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles.MediaInfo
|
|
||||||
{
|
|
||||||
public class UnknownCodecException : Exception
|
|
||||||
{
|
|
||||||
public string Codec { get; set; }
|
|
||||||
public string SceneName { get; set; }
|
|
||||||
|
|
||||||
public UnknownCodecException(string codec, string sceneName)
|
|
||||||
: base($"Unknown codec {codec}")
|
|
||||||
{
|
|
||||||
Codec = codec;
|
|
||||||
SceneName = sceneName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,7 +18,8 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
private readonly IConfigService _configService;
|
private readonly IConfigService _configService;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
private const int CURRENT_MEDIA_INFO_SCHEMA_REVISION = 3;
|
public const int MINIMUM_MEDIA_INFO_SCHEMA_REVISION = 3;
|
||||||
|
public const int CURRENT_MEDIA_INFO_SCHEMA_REVISION = 4;
|
||||||
|
|
||||||
public UpdateMediaInfoService(IDiskProvider diskProvider,
|
public UpdateMediaInfoService(IDiskProvider diskProvider,
|
||||||
IMediaFileService mediaFileService,
|
IMediaFileService mediaFileService,
|
||||||
|
@ -65,7 +66,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
var allMediaFiles = _mediaFileService.GetFilesBySeries(message.Series.Id);
|
var allMediaFiles = _mediaFileService.GetFilesBySeries(message.Series.Id);
|
||||||
var filteredMediaFiles = allMediaFiles.Where(c => c.MediaInfo == null || c.MediaInfo.SchemaRevision < CURRENT_MEDIA_INFO_SCHEMA_REVISION).ToList();
|
var filteredMediaFiles = allMediaFiles.Where(c => c.MediaInfo == null || c.MediaInfo.SchemaRevision < MINIMUM_MEDIA_INFO_SCHEMA_REVISION).ToList();
|
||||||
|
|
||||||
UpdateMediaInfo(message.Series, filteredMediaFiles);
|
UpdateMediaInfo(message.Series, filteredMediaFiles);
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,54 +104,44 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
int.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "PlayTime"), out audioRuntime);
|
int.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "PlayTime"), out audioRuntime);
|
||||||
int.TryParse(mediaInfo.Get(StreamKind.General, 0, "PlayTime"), out generalRuntime);
|
int.TryParse(mediaInfo.Get(StreamKind.General, 0, "PlayTime"), out generalRuntime);
|
||||||
|
|
||||||
string aBitRate = mediaInfo.Get(StreamKind.Audio, 0, "BitRate");
|
string aBitRate = mediaInfo.Get(StreamKind.Audio, 0, "BitRate").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
|
||||||
int aBindex = aBitRate.IndexOf(" /", StringComparison.InvariantCultureIgnoreCase);
|
|
||||||
if (aBindex > 0)
|
|
||||||
{
|
|
||||||
aBitRate = aBitRate.Remove(aBindex);
|
|
||||||
}
|
|
||||||
|
|
||||||
int.TryParse(aBitRate, out audioBitRate);
|
int.TryParse(aBitRate, out audioBitRate);
|
||||||
int.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "StreamCount"), out streamCount);
|
int.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "StreamCount"), out streamCount);
|
||||||
|
|
||||||
|
|
||||||
string audioChannelsStr = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)");
|
string audioChannelsStr = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
|
||||||
int aCindex = audioChannelsStr.IndexOf(" /", StringComparison.InvariantCultureIgnoreCase);
|
|
||||||
|
|
||||||
if (aCindex > 0)
|
|
||||||
{
|
|
||||||
audioChannelsStr = audioChannelsStr.Remove(aCindex);
|
|
||||||
}
|
|
||||||
|
|
||||||
var audioChannelPositions = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions/String2");
|
var audioChannelPositions = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions/String2");
|
||||||
var audioChannelPositionsText = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions");
|
var audioChannelPositionsText = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions");
|
||||||
|
|
||||||
string audioLanguages = mediaInfo.Get(StreamKind.General, 0, "Audio_Language_List");
|
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);
|
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();
|
||||||
if (aPindex > 0)
|
|
||||||
{
|
|
||||||
audioProfile = audioProfile.Remove(aPindex);
|
|
||||||
}
|
|
||||||
|
|
||||||
int.TryParse(audioChannelsStr, out audioChannels);
|
int.TryParse(audioChannelsStr, out audioChannels);
|
||||||
var mediaInfoModel = new MediaInfoModel
|
var mediaInfoModel = new MediaInfoModel
|
||||||
{
|
{
|
||||||
VideoCodec = mediaInfo.Get(StreamKind.Video, 0, "Codec/String"),
|
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,
|
VideoBitrate = videoBitRate,
|
||||||
VideoBitDepth = videoBitDepth,
|
VideoBitDepth = videoBitDepth,
|
||||||
Height = height,
|
Height = height,
|
||||||
Width = width,
|
Width = width,
|
||||||
AudioFormat = mediaInfo.Get(StreamKind.Audio, 0, "Format"),
|
AudioFormat = mediaInfo.Get(StreamKind.Audio, 0, "Format"),
|
||||||
|
AudioCodecID = mediaInfo.Get(StreamKind.Audio, 0, "CodecID"),
|
||||||
|
AudioProfile = audioProfile,
|
||||||
|
AudioCodecLibrary = mediaInfo.Get(StreamKind.Audio, 0, "Encoded_Library"),
|
||||||
AudioBitrate = audioBitRate,
|
AudioBitrate = audioBitRate,
|
||||||
RunTime = GetBestRuntime(audioRuntime, videoRuntime, generalRuntime),
|
RunTime = GetBestRuntime(audioRuntime, videoRuntime, generalRuntime),
|
||||||
AudioStreamCount = streamCount,
|
AudioStreamCount = streamCount,
|
||||||
AudioChannels = audioChannels,
|
AudioChannels = audioChannels,
|
||||||
AudioChannelPositions = audioChannelPositions,
|
AudioChannelPositions = audioChannelPositions,
|
||||||
AudioChannelPositionsText = audioChannelPositionsText,
|
AudioChannelPositionsText = audioChannelPositionsText,
|
||||||
AudioProfile = audioProfile.Trim(),
|
|
||||||
VideoFps = videoFrameRate,
|
VideoFps = videoFrameRate,
|
||||||
AudioLanguages = audioLanguages,
|
AudioLanguages = audioLanguages,
|
||||||
Subtitles = subtitles,
|
Subtitles = subtitles,
|
||||||
|
|
|
@ -807,7 +807,6 @@
|
||||||
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatter.cs" />
|
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatter.cs" />
|
||||||
<Compile Include="MediaFiles\MediaInfo\MediaInfoLib.cs" />
|
<Compile Include="MediaFiles\MediaInfo\MediaInfoLib.cs" />
|
||||||
<Compile Include="MediaFiles\MediaInfo\MediaInfoModel.cs" />
|
<Compile Include="MediaFiles\MediaInfo\MediaInfoModel.cs" />
|
||||||
<Compile Include="MediaFiles\MediaInfo\UnknownCodecException.cs" />
|
|
||||||
<Compile Include="MediaFiles\MediaInfo\UpdateMediaInfoService.cs" />
|
<Compile Include="MediaFiles\MediaInfo\UpdateMediaInfoService.cs" />
|
||||||
<Compile Include="MediaFiles\MediaInfo\VideoFileInfoReader.cs" />
|
<Compile Include="MediaFiles\MediaInfo\VideoFileInfoReader.cs" />
|
||||||
<Compile Include="MediaFiles\RecycleBinProvider.cs" />
|
<Compile Include="MediaFiles\RecycleBinProvider.cs" />
|
||||||
|
|
Loading…
Reference in New Issue