2018-10-30 20:44:59 +00:00
|
|
|
using System.Globalization;
|
|
|
|
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
|
|
|
|
{
|
2021-02-03 01:19:22 +00:00
|
|
|
private const string VideoDynamicRangeHdr = "HDR";
|
2021-11-09 06:31:05 +00:00
|
|
|
|
|
|
|
private static readonly Regex PositionRegex = new Regex(@"(?<position>^\d\.\d)", RegexOptions.Compiled);
|
2019-12-22 22:08:53 +00:00
|
|
|
|
2018-10-30 20:44:59 +00:00
|
|
|
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(MediaInfoFormatter));
|
|
|
|
|
|
|
|
public static decimal FormatAudioChannels(MediaInfoModel mediaInfo)
|
|
|
|
{
|
|
|
|
var audioChannels = FormatAudioChannelsFromAudioChannelPositions(mediaInfo);
|
|
|
|
|
2021-01-25 20:32:58 +00:00
|
|
|
if (audioChannels == null || audioChannels == 0.0m)
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
2021-11-09 06:31:05 +00:00
|
|
|
audioChannels = mediaInfo.AudioChannels;
|
2018-10-30 20:44:59 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
return audioChannels.Value;
|
2018-10-30 20:44:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public static string FormatAudioCodec(MediaInfoModel mediaInfo, string sceneName)
|
|
|
|
{
|
2021-11-09 06:31:05 +00:00
|
|
|
if (mediaInfo.AudioFormat == null)
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
2021-11-09 06:31:05 +00:00
|
|
|
return null;
|
2018-10-30 20:44:59 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
var audioFormat = mediaInfo.AudioFormat;
|
2018-10-30 20:44:59 +00:00
|
|
|
var audioCodecID = mediaInfo.AudioCodecID ?? string.Empty;
|
|
|
|
var audioProfile = mediaInfo.AudioProfile ?? string.Empty;
|
|
|
|
|
2019-09-04 21:24:29 +00:00
|
|
|
if (audioFormat.Empty())
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
|
|
|
return string.Empty;
|
|
|
|
}
|
2019-12-22 21:24:11 +00:00
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
// see definitions here https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/codec_desc.c
|
|
|
|
if (audioCodecID == "thd+")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
2019-09-04 21:24:29 +00:00
|
|
|
return "TrueHD Atmos";
|
2018-10-30 20:44:59 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (audioFormat == "truehd")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
2019-09-04 21:24:29 +00:00
|
|
|
return "TrueHD";
|
|
|
|
}
|
2018-10-30 20:44:59 +00:00
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (audioFormat == "flac")
|
2019-09-04 21:24:29 +00:00
|
|
|
{
|
|
|
|
return "FLAC";
|
2018-10-30 20:44:59 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (audioFormat == "dts")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
2021-11-09 06:31:05 +00:00
|
|
|
if (audioProfile == "DTS:X")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
2021-11-09 06:31:05 +00:00
|
|
|
return "DTS-X";
|
|
|
|
}
|
2019-12-22 22:08:53 +00:00
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (audioProfile == "DTS-HD MA")
|
|
|
|
{
|
2018-10-30 20:44:59 +00:00
|
|
|
return "DTS-HD MA";
|
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (audioProfile == "DTS-ES")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
|
|
|
return "DTS-ES";
|
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (audioProfile == "DTS-HD HRA")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
|
|
|
return "DTS-HD HRA";
|
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (audioProfile == "DTS Express")
|
|
|
|
{
|
|
|
|
return "DTS Express";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (audioProfile == "DTS 96/24")
|
|
|
|
{
|
|
|
|
return "DTS 96/24";
|
|
|
|
}
|
|
|
|
|
2018-10-30 20:44:59 +00:00
|
|
|
return "DTS";
|
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (audioCodecID == "ec+3")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
2021-11-09 06:31:05 +00:00
|
|
|
return "EAC3 Atmos";
|
|
|
|
}
|
2019-12-10 03:52:46 +00:00
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (audioFormat == "eac3")
|
|
|
|
{
|
2019-09-04 21:24:29 +00:00
|
|
|
return "EAC3";
|
2018-10-30 20:44:59 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (audioFormat == "ac3")
|
2019-09-04 21:24:29 +00:00
|
|
|
{
|
|
|
|
return "AC3";
|
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (audioFormat == "aac")
|
2019-09-04 21:24:29 +00:00
|
|
|
{
|
|
|
|
if (audioCodecID == "A_AAC/MPEG4/LC/SBR")
|
|
|
|
{
|
|
|
|
return "HE-AAC";
|
|
|
|
}
|
|
|
|
|
|
|
|
return "AAC";
|
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (audioFormat == "mp3")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
|
|
|
return "MP3";
|
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (audioFormat == "mp2")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
2021-11-09 06:31:05 +00:00
|
|
|
return "MP2";
|
2018-10-30 20:44:59 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (audioFormat == "opus")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
|
|
|
return "Opus";
|
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (audioFormat.StartsWith("pcm_") || audioFormat.StartsWith("adpcm_"))
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
|
|
|
return "PCM";
|
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (audioFormat == "vorbis")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
2019-09-04 21:24:29 +00:00
|
|
|
return "Vorbis";
|
2018-10-30 20:44:59 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (audioFormat == "wmav1" ||
|
2022-01-08 19:50:03 +00:00
|
|
|
audioFormat == "wmav2" ||
|
|
|
|
audioFormat == "wmapro")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
2019-09-04 21:24:29 +00:00
|
|
|
return "WMA";
|
2018-10-30 20:44:59 +00:00
|
|
|
}
|
|
|
|
|
2019-07-01 01:50:01 +00:00
|
|
|
Logger.Debug()
|
2021-11-09 06:31:05 +00:00
|
|
|
.Message("Unknown audio format: '{0}' in '{1}'.", mediaInfo.RawStreamData, sceneName)
|
|
|
|
.WriteSentryWarn("UnknownAudioFormatFFProbe", mediaInfo.ContainerFormat, mediaInfo.AudioFormat, audioCodecID)
|
2019-07-01 01:50:01 +00:00
|
|
|
.Write();
|
2018-10-30 20:44:59 +00:00
|
|
|
|
2019-09-04 21:24:29 +00:00
|
|
|
return mediaInfo.AudioFormat;
|
2018-10-30 20:44:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public static string FormatVideoCodec(MediaInfoModel mediaInfo, string sceneName)
|
|
|
|
{
|
|
|
|
if (mediaInfo.VideoFormat == null)
|
|
|
|
{
|
2021-11-09 06:31:05 +00:00
|
|
|
return null;
|
2018-10-30 20:44:59 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
var videoFormat = mediaInfo.VideoFormat;
|
2018-10-30 20:44:59 +00:00
|
|
|
var videoCodecID = mediaInfo.VideoCodecID ?? string.Empty;
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
var result = videoFormat.Trim();
|
2018-10-30 20:44:59 +00:00
|
|
|
|
2019-09-04 22:32:18 +00:00
|
|
|
if (videoFormat.Empty())
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
// see definitions here: https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/codec_desc.c
|
|
|
|
if (videoCodecID == "x264")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
|
|
|
return "x264";
|
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (videoFormat == "h264")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
|
|
|
return GetSceneNameMatch(sceneName, "AVC", "x264", "h264");
|
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (videoCodecID == "x265")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
2021-11-09 06:31:05 +00:00
|
|
|
return "x265";
|
2018-10-30 20:44:59 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (videoFormat == "hevc")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
2021-11-09 06:31:05 +00:00
|
|
|
return GetSceneNameMatch(sceneName, "HEVC", "x265", "h265");
|
2018-10-30 20:44:59 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (videoFormat == "mpeg2video")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
|
|
|
return "MPEG2";
|
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (videoFormat == "mpeg1video")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
2021-11-09 06:31:05 +00:00
|
|
|
return "MPEG";
|
2018-10-30 20:44:59 +00:00
|
|
|
}
|
|
|
|
|
2021-12-26 00:24:30 +00:00
|
|
|
if (videoFormat == "mpeg4" || videoFormat.Contains("msmpeg4"))
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
2021-11-09 06:31:05 +00:00
|
|
|
if (videoCodecID == "XVID")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
2021-11-09 06:31:05 +00:00
|
|
|
return "XviD";
|
2019-09-04 22:32:18 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (videoCodecID == "DIV3" ||
|
2022-01-26 01:01:01 +00:00
|
|
|
videoCodecID == "DX50" ||
|
|
|
|
videoCodecID.ToUpperInvariant() == "DIVX")
|
2020-08-02 03:12:06 +00:00
|
|
|
{
|
2021-11-09 06:31:05 +00:00
|
|
|
return "DivX";
|
2020-08-02 03:12:06 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
return "";
|
2018-10-30 20:44:59 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (videoFormat == "vc1")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
|
|
|
return "VC1";
|
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (videoFormat == "av1")
|
2020-05-15 03:03:03 +00:00
|
|
|
{
|
|
|
|
return "AV1";
|
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (videoFormat == "vp6" ||
|
|
|
|
videoFormat == "vp7" ||
|
|
|
|
videoFormat == "vp8" ||
|
|
|
|
videoFormat == "vp9")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
2021-11-09 06:31:05 +00:00
|
|
|
return videoFormat.ToUpperInvariant();
|
2018-10-30 20:44:59 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (videoFormat == "wmv1" ||
|
2022-01-08 19:50:03 +00:00
|
|
|
videoFormat == "wmv2" ||
|
|
|
|
videoFormat == "wmv3")
|
2018-10-30 20:44:59 +00:00
|
|
|
{
|
|
|
|
return "WMV";
|
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
if (videoFormat == "qtrle" ||
|
|
|
|
videoFormat == "rpza" ||
|
|
|
|
videoFormat == "rv10" ||
|
|
|
|
videoFormat == "rv20" ||
|
|
|
|
videoFormat == "rv30" ||
|
2022-01-26 01:01:01 +00:00
|
|
|
videoFormat == "rv40" ||
|
|
|
|
videoFormat == "cinepak")
|
2019-09-04 22:32:18 +00:00
|
|
|
{
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2019-08-07 02:20:47 +00:00
|
|
|
Logger.Debug()
|
2021-11-09 06:31:05 +00:00
|
|
|
.Message("Unknown video format: '{0}' in '{1}'.", mediaInfo.RawStreamData, sceneName)
|
|
|
|
.WriteSentryWarn("UnknownVideoFormatFFProbe", mediaInfo.ContainerFormat, videoFormat, videoCodecID)
|
2019-08-07 02:20:47 +00:00
|
|
|
.Write();
|
2018-10-30 20:44:59 +00:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static decimal? FormatAudioChannelsFromAudioChannelPositions(MediaInfoModel mediaInfo)
|
|
|
|
{
|
2021-11-09 06:31:05 +00:00
|
|
|
if (mediaInfo.AudioChannelPositions == null)
|
2021-01-25 20:32:58 +00:00
|
|
|
{
|
2021-11-09 06:31:05 +00:00
|
|
|
return 0;
|
2021-01-25 20:32:58 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
var match = PositionRegex.Match(mediaInfo.AudioChannelPositions);
|
|
|
|
if (match.Success)
|
2020-11-15 06:07:47 +00:00
|
|
|
{
|
2021-11-09 06:31:05 +00:00
|
|
|
return decimal.Parse(match.Groups["position"].Value, NumberStyles.Number, CultureInfo.InvariantCulture);
|
2020-11-15 06:07:47 +00:00
|
|
|
}
|
2018-10-30 20:44:59 +00:00
|
|
|
|
2021-11-09 06:31:05 +00:00
|
|
|
return 0;
|
2018-10-30 20:44:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
2019-07-01 01:50:01 +00:00
|
|
|
|
|
|
|
public static string FormatVideoDynamicRange(MediaInfoModel mediaInfo)
|
|
|
|
{
|
2021-11-09 06:31:05 +00:00
|
|
|
return mediaInfo.VideoHdrFormat != HdrFormat.None ? VideoDynamicRangeHdr : "";
|
2019-07-01 01:50:01 +00:00
|
|
|
}
|
2021-12-24 05:12:56 +00:00
|
|
|
|
|
|
|
public static string FormatVideoDynamicRangeType(MediaInfoModel mediaInfo)
|
|
|
|
{
|
|
|
|
switch (mediaInfo.VideoHdrFormat)
|
|
|
|
{
|
|
|
|
case HdrFormat.DolbyVision:
|
|
|
|
return "DV";
|
|
|
|
case HdrFormat.DolbyVisionHdr10:
|
|
|
|
return "DV HDR10";
|
|
|
|
case HdrFormat.DolbyVisionHlg:
|
|
|
|
return "DV HLG";
|
|
|
|
case HdrFormat.DolbyVisionSdr:
|
|
|
|
return "DV SDR";
|
|
|
|
case HdrFormat.Hdr10:
|
|
|
|
return "HDR10";
|
|
|
|
case HdrFormat.Hdr10Plus:
|
|
|
|
return "HDR10Plus";
|
|
|
|
case HdrFormat.Hlg10:
|
|
|
|
return "HLG";
|
|
|
|
case HdrFormat.Pq10:
|
|
|
|
return "PQ";
|
|
|
|
}
|
|
|
|
|
|
|
|
return "";
|
|
|
|
}
|
2018-10-30 20:44:59 +00:00
|
|
|
}
|
|
|
|
}
|