#nullable disable #pragma warning disable CS1591 using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using Jellyfin.Extensions; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Model.Entities { /// /// Class MediaStream. /// public class MediaStream { private static readonly string[] _specialCodes = { // Uncoded languages. "mis", // Multiple languages. "mul", // Undetermined. "und", // No linguistic content; not applicable. "zxx" }; /// /// Gets or sets the codec. /// /// The codec. public string Codec { get; set; } /// /// Gets or sets the codec tag. /// /// The codec tag. public string CodecTag { get; set; } /// /// Gets or sets the language. /// /// The language. public string Language { get; set; } /// /// Gets or sets the color range. /// /// The color range. public string ColorRange { get; set; } /// /// Gets or sets the color space. /// /// The color space. public string ColorSpace { get; set; } /// /// Gets or sets the color transfer. /// /// The color transfer. public string ColorTransfer { get; set; } /// /// Gets or sets the color primaries. /// /// The color primaries. public string ColorPrimaries { get; set; } /// /// Gets or sets the comment. /// /// The comment. public string Comment { get; set; } /// /// Gets or sets the time base. /// /// The time base. public string TimeBase { get; set; } /// /// Gets or sets the codec time base. /// /// The codec time base. public string CodecTimeBase { get; set; } /// /// Gets or sets the title. /// /// The title. public string Title { get; set; } /// /// Gets the video range. /// /// The video range. public string VideoRange { get { var (videoRange, videoRangeType) = getVideoColorRange(); return videoRange; } } /// /// Gets the video range type. /// /// The video range type. public string VideoRangeType { get { var (videoRange, videoRangeType) = getVideoColorRange(); return videoRangeType; } } public string LocalizedUndefined { get; set; } public string LocalizedDefault { get; set; } public string LocalizedForced { get; set; } public string LocalizedExternal { get; set; } public string DisplayTitle { get { switch (Type) { case MediaStreamType.Audio: { var attributes = new List(); // Do not display the language code in display titles if unset or set to a special code. Show it in all other cases (possibly expanded). if (!string.IsNullOrEmpty(Language) && !_specialCodes.Contains(Language, StringComparison.OrdinalIgnoreCase)) { // Get full language string i.e. eng -> English. Will not work for some languages which use ISO 639-2/B instead of /T codes. string fullLanguage = CultureInfo .GetCultures(CultureTypes.NeutralCultures) .FirstOrDefault(r => r.ThreeLetterISOLanguageName.Equals(Language, StringComparison.OrdinalIgnoreCase)) ?.DisplayName; attributes.Add(StringHelper.FirstToUpper(fullLanguage ?? Language)); } if (!string.IsNullOrEmpty(Codec) && !string.Equals(Codec, "dca", StringComparison.OrdinalIgnoreCase) && !string.Equals(Codec, "dts", StringComparison.OrdinalIgnoreCase)) { attributes.Add(AudioCodec.GetFriendlyName(Codec)); } else if (!string.IsNullOrEmpty(Profile) && !string.Equals(Profile, "lc", StringComparison.OrdinalIgnoreCase)) { attributes.Add(Profile); } if (!string.IsNullOrEmpty(ChannelLayout)) { attributes.Add(StringHelper.FirstToUpper(ChannelLayout)); } else if (Channels.HasValue) { attributes.Add(Channels.Value.ToString(CultureInfo.InvariantCulture) + " ch"); } if (IsDefault) { attributes.Add(string.IsNullOrEmpty(LocalizedDefault) ? "Default" : LocalizedDefault); } if (IsExternal) { attributes.Add(string.IsNullOrEmpty(LocalizedExternal) ? "External" : LocalizedExternal); } if (!string.IsNullOrEmpty(Title)) { var result = new StringBuilder(Title); foreach (var tag in attributes) { // Keep Tags that are not already in Title. if (!Title.Contains(tag, StringComparison.OrdinalIgnoreCase)) { result.Append(" - ").Append(tag); } } return result.ToString(); } return string.Join(" - ", attributes); } case MediaStreamType.Video: { var attributes = new List(); var resolutionText = GetResolutionText(); if (!string.IsNullOrEmpty(resolutionText)) { attributes.Add(resolutionText); } if (!string.IsNullOrEmpty(Codec)) { attributes.Add(Codec.ToUpperInvariant()); } if (!string.IsNullOrEmpty(VideoRange)) { attributes.Add(VideoRange.ToUpperInvariant()); } if (!string.IsNullOrEmpty(Title)) { var result = new StringBuilder(Title); foreach (var tag in attributes) { // Keep Tags that are not already in Title. if (!Title.Contains(tag, StringComparison.OrdinalIgnoreCase)) { result.Append(" - ").Append(tag); } } return result.ToString(); } return string.Join(' ', attributes); } case MediaStreamType.Subtitle: { var attributes = new List(); if (!string.IsNullOrEmpty(Language)) { // Get full language string i.e. eng -> English. Will not work for some languages which use ISO 639-2/B instead of /T codes. string fullLanguage = CultureInfo .GetCultures(CultureTypes.NeutralCultures) .FirstOrDefault(r => r.ThreeLetterISOLanguageName.Equals(Language, StringComparison.OrdinalIgnoreCase)) ?.DisplayName; attributes.Add(StringHelper.FirstToUpper(fullLanguage ?? Language)); } else { attributes.Add(string.IsNullOrEmpty(LocalizedUndefined) ? "Und" : LocalizedUndefined); } if (IsDefault) { attributes.Add(string.IsNullOrEmpty(LocalizedDefault) ? "Default" : LocalizedDefault); } if (IsForced) { attributes.Add(string.IsNullOrEmpty(LocalizedForced) ? "Forced" : LocalizedForced); } if (!string.IsNullOrEmpty(Codec)) { attributes.Add(Codec.ToUpperInvariant()); } if (IsExternal) { attributes.Add(string.IsNullOrEmpty(LocalizedExternal) ? "External" : LocalizedExternal); } if (!string.IsNullOrEmpty(Title)) { var result = new StringBuilder(Title); foreach (var tag in attributes) { // Keep Tags that are not already in Title. if (!Title.Contains(tag, StringComparison.OrdinalIgnoreCase)) { result.Append(" - ").Append(tag); } } return result.ToString(); } return string.Join(" - ", attributes); } default: return null; } } } public string NalLengthSize { get; set; } /// /// Gets or sets a value indicating whether this instance is interlaced. /// /// true if this instance is interlaced; otherwise, false. public bool IsInterlaced { get; set; } public bool? IsAVC { get; set; } /// /// Gets or sets the channel layout. /// /// The channel layout. public string ChannelLayout { get; set; } /// /// Gets or sets the bit rate. /// /// The bit rate. public int? BitRate { get; set; } /// /// Gets or sets the bit depth. /// /// The bit depth. public int? BitDepth { get; set; } /// /// Gets or sets the reference frames. /// /// The reference frames. public int? RefFrames { get; set; } /// /// Gets or sets the length of the packet. /// /// The length of the packet. public int? PacketLength { get; set; } /// /// Gets or sets the channels. /// /// The channels. public int? Channels { get; set; } /// /// Gets or sets the sample rate. /// /// The sample rate. public int? SampleRate { get; set; } /// /// Gets or sets a value indicating whether this instance is default. /// /// true if this instance is default; otherwise, false. public bool IsDefault { get; set; } /// /// Gets or sets a value indicating whether this instance is forced. /// /// true if this instance is forced; otherwise, false. public bool IsForced { get; set; } /// /// Gets or sets the height. /// /// The height. public int? Height { get; set; } /// /// Gets or sets the width. /// /// The width. public int? Width { get; set; } /// /// Gets or sets the average frame rate. /// /// The average frame rate. public float? AverageFrameRate { get; set; } /// /// Gets or sets the real frame rate. /// /// The real frame rate. public float? RealFrameRate { get; set; } /// /// Gets or sets the profile. /// /// The profile. public string Profile { get; set; } /// /// Gets or sets the type. /// /// The type. public MediaStreamType Type { get; set; } /// /// Gets or sets the aspect ratio. /// /// The aspect ratio. public string AspectRatio { get; set; } /// /// Gets or sets the index. /// /// The index. public int Index { get; set; } /// /// Gets or sets the score. /// /// The score. public int? Score { get; set; } /// /// Gets or sets a value indicating whether this instance is external. /// /// true if this instance is external; otherwise, false. public bool IsExternal { get; set; } /// /// Gets or sets the method. /// /// The method. public SubtitleDeliveryMethod? DeliveryMethod { get; set; } /// /// Gets or sets the delivery URL. /// /// The delivery URL. public string DeliveryUrl { get; set; } /// /// Gets or sets a value indicating whether this instance is external URL. /// /// null if [is external URL] contains no value, true if [is external URL]; otherwise, false. public bool? IsExternalUrl { get; set; } public bool IsTextSubtitleStream { get { if (Type != MediaStreamType.Subtitle) { return false; } if (string.IsNullOrEmpty(Codec) && !IsExternal) { return false; } return IsTextFormat(Codec); } } /// /// Gets or sets a value indicating whether [supports external stream]. /// /// true if [supports external stream]; otherwise, false. public bool SupportsExternalStream { get; set; } /// /// Gets or sets the filename. /// /// The filename. public string Path { get; set; } /// /// Gets or sets the pixel format. /// /// The pixel format. public string PixelFormat { get; set; } /// /// Gets or sets the level. /// /// The level. public double? Level { get; set; } /// /// Gets or sets whether this instance is anamorphic. /// /// true if this instance is anamorphic; otherwise, false. public bool? IsAnamorphic { get; set; } internal string GetResolutionText() { if (!Width.HasValue || !Height.HasValue) { return null; } return Width switch { <= 720 when Height <= 480 => IsInterlaced ? "480i" : "480p", // 720x576 (PAL) (768 when rescaled for square pixels) <= 768 when Height <= 576 => IsInterlaced ? "576i" : "576p", // 960x540 (sometimes 544 which is multiple of 16) <= 960 when Height <= 544 => IsInterlaced ? "540i" : "540p", // 1280x720 <= 1280 when Height <= 962 => IsInterlaced ? "720i" : "720p", // 1920x1080 <= 1920 when Height <= 1440 => IsInterlaced ? "1080i" : "1080p", // 4K <= 4096 when Height <= 3072 => "4K", // 8K <= 8192 when Height <= 6144 => "8K", _ => null }; } public static bool IsTextFormat(string format) { string codec = format ?? string.Empty; // sub = external .sub file return !codec.Contains("pgs", StringComparison.OrdinalIgnoreCase) && !codec.Contains("dvd", StringComparison.OrdinalIgnoreCase) && !codec.Contains("dvbsub", StringComparison.OrdinalIgnoreCase) && !string.Equals(codec, "sub", StringComparison.OrdinalIgnoreCase) && !string.Equals(codec, "dvb_subtitle", StringComparison.OrdinalIgnoreCase); } public bool SupportsSubtitleConversionTo(string toCodec) { if (!IsTextSubtitleStream) { return false; } var fromCodec = Codec; // Can't convert from this if (string.Equals(fromCodec, "ass", StringComparison.OrdinalIgnoreCase)) { return false; } if (string.Equals(fromCodec, "ssa", StringComparison.OrdinalIgnoreCase)) { return false; } // Can't convert to this if (string.Equals(toCodec, "ass", StringComparison.OrdinalIgnoreCase)) { return false; } if (string.Equals(toCodec, "ssa", StringComparison.OrdinalIgnoreCase)) { return false; } return true; } public (string VideoRange, string VideoRangeType) getVideoColorRange() { if (Type != MediaStreamType.Video) { return (null, null); } var colorTransfer = ColorTransfer; if (string.Equals(colorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)) { return ("HDR", "HDR10"); } if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) { return ("HDR", "HLG"); } // For some Dolby Vision files, no color transfer is provided, so check the codec var codecTag = CodecTag; if (string.Equals(codecTag, "dovi", StringComparison.OrdinalIgnoreCase) || string.Equals(codecTag, "dvh1", StringComparison.OrdinalIgnoreCase) || string.Equals(codecTag, "dvhe", StringComparison.OrdinalIgnoreCase) || string.Equals(codecTag, "dav1", StringComparison.OrdinalIgnoreCase)) { return ("HDR", "DOVI"); } return ("SDR", "SDR"); } } }