< Summary - Jellyfin

Information
Class: MediaBrowser.Model.Entities.MediaStream
Assembly: MediaBrowser.Model
File(s): /srv/git/jellyfin/MediaBrowser.Model/Entities/MediaStream.cs
Line coverage
73%
Covered lines: 165
Uncovered lines: 59
Coverable lines: 224
Total lines: 851
Line coverage: 73.6%
Branch coverage
65%
Covered branches: 196
Total branches: 300
Branch coverage: 65.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 2/13/2026 - 12:11:21 AM Line coverage: 73.8% (164/222) Branch coverage: 66.8% (198/296) Total lines: 8403/31/2026 - 12:14:24 AM Line coverage: 73.8% (164/222) Branch coverage: 66.8% (198/296) Total lines: 8394/12/2026 - 12:13:54 AM Line coverage: 73.8% (164/222) Branch coverage: 68.2% (202/296) Total lines: 8385/8/2026 - 12:15:13 AM Line coverage: 73.6% (165/224) Branch coverage: 67.6% (203/300) Total lines: 8515/20/2026 - 12:15:44 AM Line coverage: 73.6% (165/224) Branch coverage: 65.3% (196/300) Total lines: 851 2/13/2026 - 12:11:21 AM Line coverage: 73.8% (164/222) Branch coverage: 66.8% (198/296) Total lines: 8403/31/2026 - 12:14:24 AM Line coverage: 73.8% (164/222) Branch coverage: 66.8% (198/296) Total lines: 8394/12/2026 - 12:13:54 AM Line coverage: 73.8% (164/222) Branch coverage: 68.2% (202/296) Total lines: 8385/8/2026 - 12:15:13 AM Line coverage: 73.6% (165/224) Branch coverage: 67.6% (203/300) Total lines: 8515/20/2026 - 12:15:44 AM Line coverage: 73.6% (165/224) Branch coverage: 65.3% (196/300) Total lines: 851

Coverage delta

Coverage delta 3 -3

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
get_VideoRange()100%11100%
get_VideoRangeType()100%11100%
get_VideoDoViTitle()8%852554.16%
get_AudioSpatialFormat()75%88100%
get_DisplayTitle()60.97%3208267.18%
get_ReferenceFrameRate()100%22100%
get_IsTextSubtitleStream()100%66100%
get_IsPgsSubtitleStream()100%66100%
get_IsExtractableSubtitleStream()100%22100%
GetResolutionText()93.24%747496.66%
IsTextFormat(...)83.33%1212100%
IsPgsFormat(...)50%44100%
SupportsSubtitleConversionTo(...)50%171058.33%
GetVideoColorRange()52.17%4016958.82%

File(s)

/srv/git/jellyfin/MediaBrowser.Model/Entities/MediaStream.cs

#LineLine coverage
 1#nullable disable
 2#pragma warning disable CS1591
 3
 4using System;
 5using System.Collections.Generic;
 6using System.ComponentModel;
 7using System.Globalization;
 8using System.Text;
 9using System.Text.Json.Serialization;
 10using Jellyfin.Data.Enums;
 11using Jellyfin.Extensions;
 12using MediaBrowser.Model.Dlna;
 13using MediaBrowser.Model.Extensions;
 14using MediaBrowser.Model.MediaInfo;
 15
 16namespace MediaBrowser.Model.Entities
 17{
 18    /// <summary>
 19    /// Class MediaStream.
 20    /// </summary>
 21    public class MediaStream
 22    {
 223        private static readonly string[] _specialCodes =
 224        {
 225            // Uncoded languages.
 226            "mis",
 227            // Multiple languages.
 228            "mul",
 229            // Undetermined.
 230            "und",
 231            // No linguistic content; not applicable.
 232            "zxx"
 233        };
 34
 35        /// <summary>
 36        /// Gets or sets the codec.
 37        /// </summary>
 38        /// <value>The codec.</value>
 39        public string Codec { get; set; }
 40
 41        /// <summary>
 42        /// Gets or sets the codec tag.
 43        /// </summary>
 44        /// <value>The codec tag.</value>
 45        public string CodecTag { get; set; }
 46
 47        /// <summary>
 48        /// Gets or sets the language.
 49        /// </summary>
 50        /// <value>The language.</value>
 51        public string Language { get; set; }
 52
 53        /// <summary>
 54        /// Gets or sets the color range.
 55        /// </summary>
 56        /// <value>The color range.</value>
 57        public string ColorRange { get; set; }
 58
 59        /// <summary>
 60        /// Gets or sets the color space.
 61        /// </summary>
 62        /// <value>The color space.</value>
 63        public string ColorSpace { get; set; }
 64
 65        /// <summary>
 66        /// Gets or sets the color transfer.
 67        /// </summary>
 68        /// <value>The color transfer.</value>
 69        public string ColorTransfer { get; set; }
 70
 71        /// <summary>
 72        /// Gets or sets the color primaries.
 73        /// </summary>
 74        /// <value>The color primaries.</value>
 75        public string ColorPrimaries { get; set; }
 76
 77        /// <summary>
 78        /// Gets or sets the Dolby Vision version major.
 79        /// </summary>
 80        /// <value>The Dolby Vision version major.</value>
 81        public int? DvVersionMajor { get; set; }
 82
 83        /// <summary>
 84        /// Gets or sets the Dolby Vision version minor.
 85        /// </summary>
 86        /// <value>The Dolby Vision version minor.</value>
 87        public int? DvVersionMinor { get; set; }
 88
 89        /// <summary>
 90        /// Gets or sets the Dolby Vision profile.
 91        /// </summary>
 92        /// <value>The Dolby Vision profile.</value>
 93        public int? DvProfile { get; set; }
 94
 95        /// <summary>
 96        /// Gets or sets the Dolby Vision level.
 97        /// </summary>
 98        /// <value>The Dolby Vision level.</value>
 99        public int? DvLevel { get; set; }
 100
 101        /// <summary>
 102        /// Gets or sets the Dolby Vision rpu present flag.
 103        /// </summary>
 104        /// <value>The Dolby Vision rpu present flag.</value>
 105        public int? RpuPresentFlag { get; set; }
 106
 107        /// <summary>
 108        /// Gets or sets the Dolby Vision el present flag.
 109        /// </summary>
 110        /// <value>The Dolby Vision el present flag.</value>
 111        public int? ElPresentFlag { get; set; }
 112
 113        /// <summary>
 114        /// Gets or sets the Dolby Vision bl present flag.
 115        /// </summary>
 116        /// <value>The Dolby Vision bl present flag.</value>
 117        public int? BlPresentFlag { get; set; }
 118
 119        /// <summary>
 120        /// Gets or sets the Dolby Vision bl signal compatibility id.
 121        /// </summary>
 122        /// <value>The Dolby Vision bl signal compatibility id.</value>
 123        public int? DvBlSignalCompatibilityId { get; set; }
 124
 125        /// <summary>
 126        /// Gets or sets the Rotation in degrees.
 127        /// </summary>
 128        /// <value>The video rotation.</value>
 129        public int? Rotation { get; set; }
 130
 131        /// <summary>
 132        /// Gets or sets the comment.
 133        /// </summary>
 134        /// <value>The comment.</value>
 135        public string Comment { get; set; }
 136
 137        /// <summary>
 138        /// Gets or sets the time base.
 139        /// </summary>
 140        /// <value>The time base.</value>
 141        public string TimeBase { get; set; }
 142
 143        /// <summary>
 144        /// Gets or sets the codec time base.
 145        /// </summary>
 146        /// <value>The codec time base.</value>
 147        public string CodecTimeBase { get; set; }
 148
 149        /// <summary>
 150        /// Gets or sets the title.
 151        /// </summary>
 152        /// <value>The title.</value>
 153        public string Title { get; set; }
 154
 155        public bool? Hdr10PlusPresentFlag { get; set; }
 156
 157        /// <summary>
 158        /// Gets the video range.
 159        /// </summary>
 160        /// <value>The video range.</value>
 161        [DefaultValue(VideoRange.Unknown)]
 162        public VideoRange VideoRange
 163        {
 164            get
 165            {
 35166                var (videoRange, _) = GetVideoColorRange();
 167
 35168                return videoRange;
 169            }
 170        }
 171
 172        /// <summary>
 173        /// Gets the video range type.
 174        /// </summary>
 175        /// <value>The video range type.</value>
 176        [DefaultValue(VideoRangeType.Unknown)]
 177        public VideoRangeType VideoRangeType
 178        {
 179            get
 180            {
 912181                var (_, videoRangeType) = GetVideoColorRange();
 182
 912183                return videoRangeType;
 184            }
 185        }
 186
 187        /// <summary>
 188        /// Gets the video dovi title.
 189        /// </summary>
 190        /// <value>The video dovi title.</value>
 191        public string VideoDoViTitle
 192        {
 193            get
 194            {
 35195                var dvProfile = DvProfile;
 35196                var rpuPresentFlag = RpuPresentFlag == 1;
 35197                var blPresentFlag = BlPresentFlag == 1;
 35198                var dvBlCompatId = DvBlSignalCompatibilityId;
 199
 35200                if (rpuPresentFlag
 35201                    && blPresentFlag
 35202                    && (dvProfile == 4
 35203                        || dvProfile == 5
 35204                        || dvProfile == 7
 35205                        || dvProfile == 8
 35206                        || dvProfile == 9
 35207                        || dvProfile == 10))
 208                {
 0209                    var title = "Dolby Vision Profile " + dvProfile;
 210
 0211                    if (dvBlCompatId > 0)
 212                    {
 0213                        title += "." + dvBlCompatId;
 214                    }
 215
 0216                    return dvBlCompatId switch
 0217                    {
 0218                        1 => title + " (HDR10)",
 0219                        2 => title + " (SDR)",
 0220                        4 => title + " (HLG)",
 0221                        6 => title + " (HDR10)", // Technically means Blu-ray, but practically always HDR10
 0222                        _ => title
 0223                    };
 224                }
 225
 35226                return null;
 227            }
 228        }
 229
 230        /// <summary>
 231        /// Gets the audio spatial format.
 232        /// </summary>
 233        /// <value>The audio spatial format.</value>
 234        [DefaultValue(AudioSpatialFormat.None)]
 235        public AudioSpatialFormat AudioSpatialFormat
 236        {
 237            get
 238            {
 37239                if (Type != MediaStreamType.Audio || string.IsNullOrEmpty(Profile))
 240                {
 35241                    return AudioSpatialFormat.None;
 242                }
 243
 2244                return
 2245                    Profile.Contains("Dolby Atmos", StringComparison.OrdinalIgnoreCase) ? AudioSpatialFormat.DolbyAtmos 
 2246                    Profile.Contains("DTS:X", StringComparison.OrdinalIgnoreCase) ? AudioSpatialFormat.DTSX :
 2247                    AudioSpatialFormat.None;
 248            }
 249        }
 250
 251        public string LocalizedUndefined { get; set; }
 252
 253        public string LocalizedDefault { get; set; }
 254
 255        public string LocalizedForced { get; set; }
 256
 257        public string LocalizedExternal { get; set; }
 258
 259        public string LocalizedHearingImpaired { get; set; }
 260
 261        public string LocalizedLanguage { get; set; }
 262
 263        public string LocalizedOriginal { get; set; }
 264
 265        public string DisplayTitle
 266        {
 267            get
 268            {
 46269                switch (Type)
 270                {
 271                    case MediaStreamType.Audio:
 272                        {
 16273                            var attributes = new List<string>();
 274
 275                            // Do not display the language code in display titles if unset or set to a special code. Sho
 16276                            if (!string.IsNullOrEmpty(Language) && !_specialCodes.Contains(Language, StringComparison.Or
 277                            {
 278                                // Use pre-resolved localized language name, falling back to raw language code.
 5279                                attributes.Add(StringHelper.FirstToUpper(LocalizedLanguage ?? Language));
 280                            }
 281
 16282                            if (!string.IsNullOrEmpty(Profile) && !string.Equals(Profile, "lc", StringComparison.Ordinal
 283                            {
 0284                                attributes.Add(Profile);
 285                            }
 16286                            else if (!string.IsNullOrEmpty(Codec))
 287                            {
 4288                                attributes.Add(AudioCodec.GetFriendlyName(Codec));
 289                            }
 290
 16291                            if (!string.IsNullOrEmpty(ChannelLayout))
 292                            {
 2293                                attributes.Add(StringHelper.FirstToUpper(ChannelLayout));
 294                            }
 14295                            else if (Channels.HasValue)
 296                            {
 0297                                attributes.Add(Channels.Value.ToString(CultureInfo.InvariantCulture) + " ch");
 298                            }
 299
 16300                            if (IsDefault)
 301                            {
 4302                                attributes.Add(string.IsNullOrEmpty(LocalizedDefault) ? "Default" : LocalizedDefault);
 303                            }
 304
 16305                            if (IsExternal)
 306                            {
 8307                                attributes.Add(string.IsNullOrEmpty(LocalizedExternal) ? "External" : LocalizedExternal)
 308                            }
 309
 16310                            if (IsOriginal)
 311                            {
 0312                                attributes.Add(string.IsNullOrEmpty(LocalizedOriginal) ? "Original" : LocalizedOriginal)
 313                            }
 314
 16315                            if (!string.IsNullOrEmpty(Title))
 316                            {
 2317                                var result = new StringBuilder(Title);
 16318                                foreach (var tag in attributes)
 319                                {
 320                                    // Keep Tags that are not already in Title.
 6321                                    if (!Title.Contains(tag, StringComparison.OrdinalIgnoreCase))
 322                                    {
 6323                                        result.Append(" - ").Append(tag);
 324                                    }
 325                                }
 326
 2327                                return result.ToString();
 328                            }
 329
 14330                            return string.Join(" - ", attributes);
 331                        }
 332
 333                    case MediaStreamType.Video:
 334                        {
 0335                            var attributes = new List<string>();
 336
 0337                            var resolutionText = GetResolutionText();
 338
 0339                            if (!string.IsNullOrEmpty(resolutionText))
 340                            {
 0341                                attributes.Add(resolutionText);
 342                            }
 343
 0344                            if (!string.IsNullOrEmpty(Codec))
 345                            {
 0346                                attributes.Add(Codec.ToUpperInvariant());
 347                            }
 348
 0349                            if (VideoDoViTitle is not null)
 350                            {
 0351                                attributes.Add(VideoDoViTitle);
 352                            }
 0353                            else if (VideoRange != VideoRange.Unknown)
 354                            {
 0355                                attributes.Add(VideoRange.ToString());
 356                            }
 357
 0358                            if (!string.IsNullOrEmpty(Title))
 359                            {
 0360                                var result = new StringBuilder(Title);
 0361                                foreach (var tag in attributes)
 362                                {
 363                                    // Keep Tags that are not already in Title.
 0364                                    if (!Title.Contains(tag, StringComparison.OrdinalIgnoreCase))
 365                                    {
 0366                                        result.Append(" - ").Append(tag);
 367                                    }
 368                                }
 369
 0370                                return result.ToString();
 371                            }
 372
 0373                            return string.Join(' ', attributes);
 374                        }
 375
 376                    case MediaStreamType.Subtitle:
 377                        {
 30378                            var attributes = new List<string>();
 379
 30380                            if (!string.IsNullOrEmpty(Language))
 381                            {
 382                                // Use pre-resolved localized language name, falling back to raw language code.
 20383                                attributes.Add(StringHelper.FirstToUpper(LocalizedLanguage ?? Language));
 384                            }
 385                            else
 386                            {
 10387                                attributes.Add(string.IsNullOrEmpty(LocalizedUndefined) ? "Und" : LocalizedUndefined);
 388                            }
 389
 30390                            if (IsHearingImpaired == true)
 391                            {
 6392                                attributes.Add(string.IsNullOrEmpty(LocalizedHearingImpaired) ? "Hearing Impaired" : Loc
 393                            }
 394
 30395                            if (IsDefault)
 396                            {
 11397                                attributes.Add(string.IsNullOrEmpty(LocalizedDefault) ? "Default" : LocalizedDefault);
 398                            }
 399
 30400                            if (IsForced)
 401                            {
 12402                                attributes.Add(string.IsNullOrEmpty(LocalizedForced) ? "Forced" : LocalizedForced);
 403                            }
 404
 30405                            if (!string.IsNullOrEmpty(Codec))
 406                            {
 12407                                attributes.Add(Codec.ToUpperInvariant());
 408                            }
 409
 30410                            if (IsExternal)
 411                            {
 2412                                attributes.Add(string.IsNullOrEmpty(LocalizedExternal) ? "External" : LocalizedExternal)
 413                            }
 414
 30415                            if (!string.IsNullOrEmpty(Title))
 416                            {
 20417                                var result = new StringBuilder(Title);
 154418                                foreach (var tag in attributes)
 419                                {
 420                                    // Keep Tags that are not already in Title.
 57421                                    if (!Title.Contains(tag, StringComparison.OrdinalIgnoreCase))
 422                                    {
 53423                                        result.Append(" - ").Append(tag);
 424                                    }
 425                                }
 426
 20427                                return result.ToString();
 428                            }
 429
 10430                            return string.Join(" - ", attributes);
 431                        }
 432
 433                    default:
 0434                        return null;
 435                }
 436            }
 437        }
 438
 439        public string NalLengthSize { get; set; }
 440
 441        /// <summary>
 442        /// Gets or sets a value indicating whether this instance is interlaced.
 443        /// </summary>
 444        /// <value><c>true</c> if this instance is interlaced; otherwise, <c>false</c>.</value>
 445        public bool IsInterlaced { get; set; }
 446
 447        public bool? IsAVC { get; set; }
 448
 449        /// <summary>
 450        /// Gets or sets the channel layout.
 451        /// </summary>
 452        /// <value>The channel layout.</value>
 453        public string ChannelLayout { get; set; }
 454
 455        /// <summary>
 456        /// Gets or sets the bit rate.
 457        /// </summary>
 458        /// <value>The bit rate.</value>
 459        public int? BitRate { get; set; }
 460
 461        /// <summary>
 462        /// Gets or sets the bit depth.
 463        /// </summary>
 464        /// <value>The bit depth.</value>
 465        public int? BitDepth { get; set; }
 466
 467        /// <summary>
 468        /// Gets or sets the reference frames.
 469        /// </summary>
 470        /// <value>The reference frames.</value>
 471        public int? RefFrames { get; set; }
 472
 473        /// <summary>
 474        /// Gets or sets the length of the packet.
 475        /// </summary>
 476        /// <value>The length of the packet.</value>
 477        public int? PacketLength { get; set; }
 478
 479        /// <summary>
 480        /// Gets or sets the channels.
 481        /// </summary>
 482        /// <value>The channels.</value>
 483        public int? Channels { get; set; }
 484
 485        /// <summary>
 486        /// Gets or sets the sample rate.
 487        /// </summary>
 488        /// <value>The sample rate.</value>
 489        public int? SampleRate { get; set; }
 490
 491        /// <summary>
 492        /// Gets or sets a value indicating whether this instance is default.
 493        /// </summary>
 494        /// <value><c>true</c> if this instance is default; otherwise, <c>false</c>.</value>
 495        public bool IsDefault { get; set; }
 496
 497        /// <summary>
 498        /// Gets or sets a value indicating whether this instance is forced.
 499        /// </summary>
 500        /// <value><c>true</c> if this instance is forced; otherwise, <c>false</c>.</value>
 501        public bool IsForced { get; set; }
 502
 503        /// <summary>
 504        /// Gets or sets a value indicating whether this instance is for the hearing impaired.
 505        /// </summary>
 506        /// <value><c>true</c> if this instance is for the hearing impaired; otherwise, <c>false</c>.</value>
 507        public bool IsHearingImpaired { get; set; }
 508
 509        /// <summary>
 510        /// Gets or sets a value indicating whether this instance is original.
 511        /// </summary>
 512        /// <value><c>true</c> if this instance is original; otherwise, <c>false</c>.</value>
 513        public bool IsOriginal { get; set; }
 514
 515        /// <summary>
 516        /// Gets or sets the height.
 517        /// </summary>
 518        /// <value>The height.</value>
 519        public int? Height { get; set; }
 520
 521        /// <summary>
 522        /// Gets or sets the width.
 523        /// </summary>
 524        /// <value>The width.</value>
 525        public int? Width { get; set; }
 526
 527        /// <summary>
 528        /// Gets or sets the average frame rate.
 529        /// </summary>
 530        /// <value>The average frame rate.</value>
 531        public float? AverageFrameRate { get; set; }
 532
 533        /// <summary>
 534        /// Gets or sets the real frame rate.
 535        /// </summary>
 536        /// <value>The real frame rate.</value>
 537        public float? RealFrameRate { get; set; }
 538
 539        /// <summary>
 540        /// Gets the framerate used as reference.
 541        /// Prefer AverageFrameRate, if that is null or an unrealistic value
 542        /// then fallback to RealFrameRate.
 543        /// </summary>
 544        /// <value>The reference frame rate.</value>
 545        public float? ReferenceFrameRate
 546        {
 547            get
 548            {
 549                // In some cases AverageFrameRate for videos will be read as 1000fps even if it is not.
 550                // This is probably due to a library compatibility issue.
 551                // See https://github.com/jellyfin/jellyfin/pull/12603#discussion_r1748044018 for more info.
 1058552                return AverageFrameRate < 1000 ? AverageFrameRate : RealFrameRate;
 553            }
 554        }
 555
 556        /// <summary>
 557        /// Gets or sets the profile.
 558        /// </summary>
 559        /// <value>The profile.</value>
 560        public string Profile { get; set; }
 561
 562        /// <summary>
 563        /// Gets or sets the type.
 564        /// </summary>
 565        /// <value>The type.</value>
 566        public MediaStreamType Type { get; set; }
 567
 568        /// <summary>
 569        /// Gets or sets the aspect ratio.
 570        /// </summary>
 571        /// <value>The aspect ratio.</value>
 572        public string AspectRatio { get; set; }
 573
 574        /// <summary>
 575        /// Gets or sets the index.
 576        /// </summary>
 577        /// <value>The index.</value>
 578        public int Index { get; set; }
 579
 580        /// <summary>
 581        /// Gets or sets the score.
 582        /// </summary>
 583        /// <value>The score.</value>
 584        public int? Score { get; set; }
 585
 586        /// <summary>
 587        /// Gets or sets a value indicating whether this instance is external.
 588        /// </summary>
 589        /// <value><c>true</c> if this instance is external; otherwise, <c>false</c>.</value>
 590        public bool IsExternal { get; set; }
 591
 592        /// <summary>
 593        /// Gets or sets the method.
 594        /// </summary>
 595        /// <value>The method.</value>
 596        public SubtitleDeliveryMethod? DeliveryMethod { get; set; }
 597
 598        /// <summary>
 599        /// Gets or sets the delivery URL.
 600        /// </summary>
 601        /// <value>The delivery URL.</value>
 602        public string DeliveryUrl { get; set; }
 603
 604        /// <summary>
 605        /// Gets or sets a value indicating whether this instance is external URL.
 606        /// </summary>
 607        /// <value><c>null</c> if [is external URL] contains no value, <c>true</c> if [is external URL]; otherwise, <c>f
 608        public bool? IsExternalUrl { get; set; }
 609
 610        public bool IsTextSubtitleStream
 611        {
 612            get
 613            {
 2166614                if (Type != MediaStreamType.Subtitle)
 615                {
 146616                    return false;
 617                }
 618
 2020619                if (string.IsNullOrEmpty(Codec) && !IsExternal)
 620                {
 30621                    return false;
 622                }
 623
 1990624                return IsTextFormat(Codec);
 625            }
 626        }
 627
 628        [JsonIgnore]
 629        public bool IsPgsSubtitleStream
 630        {
 631            get
 632            {
 64633                if (Type != MediaStreamType.Subtitle)
 634                {
 28635                    return false;
 636                }
 637
 36638                if (string.IsNullOrEmpty(Codec) && !IsExternal)
 639                {
 30640                    return false;
 641                }
 642
 6643                return IsPgsFormat(Codec);
 644            }
 645        }
 646
 647        /// <summary>
 648        /// Gets a value indicating whether this is a subtitle steam that is extractable by ffmpeg.
 649        /// All text-based and pgs subtitles can be extracted.
 650        /// </summary>
 651        /// <value><c>true</c> if this is a extractable subtitle steam otherwise, <c>false</c>.</value>
 652        [JsonIgnore]
 35653        public bool IsExtractableSubtitleStream => IsTextSubtitleStream || IsPgsSubtitleStream;
 654
 655        /// <summary>
 656        /// Gets or sets a value indicating whether [supports external stream].
 657        /// </summary>
 658        /// <value><c>true</c> if [supports external stream]; otherwise, <c>false</c>.</value>
 659        public bool SupportsExternalStream { get; set; }
 660
 661        /// <summary>
 662        /// Gets or sets the filename.
 663        /// </summary>
 664        /// <value>The filename.</value>
 665        public string Path { get; set; }
 666
 667        /// <summary>
 668        /// Gets or sets the pixel format.
 669        /// </summary>
 670        /// <value>The pixel format.</value>
 671        public string PixelFormat { get; set; }
 672
 673        /// <summary>
 674        /// Gets or sets the level.
 675        /// </summary>
 676        /// <value>The level.</value>
 677        public double? Level { get; set; }
 678
 679        /// <summary>
 680        /// Gets or sets whether this instance is anamorphic.
 681        /// </summary>
 682        /// <value><c>true</c> if this instance is anamorphic; otherwise, <c>false</c>.</value>
 683        public bool? IsAnamorphic { get; set; }
 684
 685        internal string GetResolutionText()
 686        {
 73687            if (!Width.HasValue || !Height.HasValue)
 688            {
 3689                return null;
 690            }
 691
 70692            return Width switch
 70693            {
 70694                // 256x144 (16:9 square pixel format)
 4695                <= 256 when Height <= 144 => IsInterlaced ? "144i" : "144p",
 70696                // 426x240 (16:9 square pixel format)
 4697                <= 426 when Height <= 240 => IsInterlaced ? "240i" : "240p",
 70698                // 640x360 (16:9 square pixel format)
 14699                <= 640 when Height <= 360 => IsInterlaced ? "360i" : "360p",
 70700                // 682x384 (16:9 square pixel format)
 3701                <= 682 when Height <= 384 => IsInterlaced ? "384i" : "384p",
 70702                // 720x404 (16:9 square pixel format)
 7703                <= 720 when Height <= 404 => IsInterlaced ? "404i" : "404p",
 70704                // 854x480 (16:9 square pixel format)
 10705                <= 854 when Height <= 480 => IsInterlaced ? "480i" : "480p",
 70706                // 960x544 (16:9 square pixel format)
 11707                <= 960 when Height <= 544 => IsInterlaced ? "540i" : "540p",
 70708                // 1024x576 (16:9 square pixel format)
 9709                <= 1024 when Height <= 576 => IsInterlaced ? "576i" : "576p",
 70710                // 1280x720
 26711                <= 1280 when Height <= 962 => IsInterlaced ? "720i" : "720p",
 70712                // 2560x1080 (FHD ultra wide 21:9) using 1440px width to accommodate WQHD
 46713                <= 2560 when Height <= 1440 => IsInterlaced ? "1080i" : "1080p",
 70714                // 4K
 12715                <= 4096 when Height <= 3072 => "4K",
 70716                // 8K
 6717                <= 8192 when Height <= 6144 => "8K",
 0718                _ => null
 70719            };
 720        }
 721
 722        public static bool IsTextFormat(string format)
 723        {
 3401724            string codec = format ?? string.Empty;
 725
 726            // microdvd and dvdsub/vobsub share the ".sub" file extension, but it's text-based.
 727
 3401728            return codec.Contains("microdvd", StringComparison.OrdinalIgnoreCase)
 3401729                   || (!codec.Contains("pgs", StringComparison.OrdinalIgnoreCase)
 3401730                       && !codec.Contains("dvdsub", StringComparison.OrdinalIgnoreCase)
 3401731                       && !codec.Contains("dvbsub", StringComparison.OrdinalIgnoreCase)
 3401732                       && !string.Equals(codec, "sup", StringComparison.OrdinalIgnoreCase)
 3401733                       && !string.Equals(codec, "sub", StringComparison.OrdinalIgnoreCase));
 734        }
 735
 736        public static bool IsPgsFormat(string format)
 737        {
 10738            string codec = format ?? string.Empty;
 739
 10740            return codec.Contains("pgs", StringComparison.OrdinalIgnoreCase)
 10741                   || string.Equals(codec, "sup", StringComparison.OrdinalIgnoreCase);
 742        }
 743
 744        public bool SupportsSubtitleConversionTo(string toCodec)
 745        {
 286746            if (!IsTextSubtitleStream)
 747            {
 0748                return false;
 749            }
 750
 286751            var fromCodec = Codec;
 752
 753            // Can't convert from this
 286754            if (string.Equals(fromCodec, "ass", StringComparison.OrdinalIgnoreCase))
 755            {
 0756                return false;
 757            }
 758
 286759            if (string.Equals(fromCodec, "ssa", StringComparison.OrdinalIgnoreCase))
 760            {
 0761                return false;
 762            }
 763
 764            // Can't convert to this
 286765            if (string.Equals(toCodec, "ass", StringComparison.OrdinalIgnoreCase))
 766            {
 0767                return false;
 768            }
 769
 286770            if (string.Equals(toCodec, "ssa", StringComparison.OrdinalIgnoreCase))
 771            {
 0772                return false;
 773            }
 774
 286775            return true;
 776        }
 777
 778        public (VideoRange VideoRange, VideoRangeType VideoRangeType) GetVideoColorRange()
 779        {
 947780            if (Type != MediaStreamType.Video)
 781            {
 70782                return (VideoRange.Unknown, VideoRangeType.Unknown);
 783            }
 784
 877785            var codecTag = CodecTag;
 877786            var dvProfile = DvProfile;
 877787            var rpuPresentFlag = RpuPresentFlag == 1;
 877788            var blPresentFlag = BlPresentFlag == 1;
 877789            var dvBlCompatId = DvBlSignalCompatibilityId;
 790
 877791            var isDoViProfile = dvProfile is 5 or 7 or 8 or 10;
 877792            var isDoViFlag = rpuPresentFlag && blPresentFlag && dvBlCompatId is 0 or 1 or 4 or 2 or 6;
 793
 877794            if ((isDoViProfile && isDoViFlag)
 877795                || string.Equals(codecTag, "dovi", StringComparison.OrdinalIgnoreCase)
 877796                || string.Equals(codecTag, "dvh1", StringComparison.OrdinalIgnoreCase)
 877797                || string.Equals(codecTag, "dvhe", StringComparison.OrdinalIgnoreCase)
 877798                || string.Equals(codecTag, "dav1", StringComparison.OrdinalIgnoreCase))
 799            {
 115800                var dvRangeSet = dvProfile switch
 115801                {
 64802                    5 => (VideoRange.HDR, VideoRangeType.DOVI),
 51803                    8 => dvBlCompatId switch
 51804                    {
 51805                        1 => (VideoRange.HDR, VideoRangeType.DOVIWithHDR10),
 0806                        4 => (VideoRange.HDR, VideoRangeType.DOVIWithHLG),
 0807                        2 => (VideoRange.SDR, VideoRangeType.DOVIWithSDR),
 51808                        // Out of Dolby Spec files should be marked as invalid
 0809                        _ => (VideoRange.HDR, VideoRangeType.DOVIInvalid)
 51810                    },
 0811                    7 => (VideoRange.HDR, VideoRangeType.DOVIWithEL),
 0812                    10 => dvBlCompatId switch
 0813                    {
 0814                        0 => (VideoRange.HDR, VideoRangeType.DOVI),
 0815                        1 => (VideoRange.HDR, VideoRangeType.DOVIWithHDR10),
 0816                        2 => (VideoRange.SDR, VideoRangeType.DOVIWithSDR),
 0817                        4 => (VideoRange.HDR, VideoRangeType.DOVIWithHLG),
 0818                        // Out of Dolby Spec files should be marked as invalid
 0819                        _ => (VideoRange.HDR, VideoRangeType.DOVIInvalid)
 0820                    },
 0821                    _ => (VideoRange.SDR, VideoRangeType.SDR)
 115822                };
 823
 115824                if (Hdr10PlusPresentFlag == true)
 825                {
 0826                    return dvRangeSet.Item2 switch
 0827                    {
 0828                        VideoRangeType.DOVIWithHDR10 => (VideoRange.HDR, VideoRangeType.DOVIWithHDR10Plus),
 0829                        VideoRangeType.DOVIWithEL => (VideoRange.HDR, VideoRangeType.DOVIWithELHDR10Plus),
 0830                        _ => dvRangeSet
 0831                    };
 832                }
 833
 115834                return dvRangeSet;
 835            }
 836
 762837            var colorTransfer = ColorTransfer;
 838
 762839            if (string.Equals(colorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase))
 840            {
 198841                return Hdr10PlusPresentFlag == true ? (VideoRange.HDR, VideoRangeType.HDR10Plus) : (VideoRange.HDR, Vide
 842            }
 564843            else if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
 844            {
 0845                return (VideoRange.HDR, VideoRangeType.HLG);
 846            }
 847
 564848            return (VideoRange.SDR, VideoRangeType.SDR);
 849        }
 850    }
 851}