< Summary - Jellyfin

Information
Class: Jellyfin.Api.Helpers.HlsCodecStringHelpers
Assembly: Jellyfin.Api
File(s): /srv/git/jellyfin/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 106
Coverable lines: 106
Total lines: 349
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 124
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 10/5/2025 - 12:11:27 AM Line coverage: 0% (0/93) Branch coverage: 0% (0/108) Total lines: 3041/10/2026 - 12:12:36 AM Line coverage: 0% (0/106) Branch coverage: 0% (0/124) Total lines: 349 10/5/2025 - 12:11:27 AM Line coverage: 0% (0/93) Branch coverage: 0% (0/108) Total lines: 3041/10/2026 - 12:12:36 AM Line coverage: 0% (0/106) Branch coverage: 0% (0/124) Total lines: 349

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GetMP3String()100%210%
GetAACString(...)0%620%
GetAC3String()100%210%
GetEAC3String()100%210%
GetFLACString()100%210%
GetALACString()100%210%
GetOPUSString()100%210%
GetTRUEHDString()100%210%
GetDTSString(...)0%272160%
GetH264String(...)0%4260%
GetH265String(...)0%2040%
GetVp9String(...)0%5852760%
GetAv1String(...)0%420200%

File(s)

/srv/git/jellyfin/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs

#LineLine coverage
 1using System;
 2using System.Globalization;
 3using System.Text;
 4
 5namespace Jellyfin.Api.Helpers;
 6
 7/// <summary>
 8/// Helpers to generate HLS codec strings according to
 9/// <a href="https://datatracker.ietf.org/doc/html/rfc6381#section-3.3">RFC 6381 section 3.3</a>
 10/// and the <a href="https://mp4ra.org">MP4 Registration Authority</a>.
 11/// </summary>
 12public static class HlsCodecStringHelpers
 13{
 14    /// <summary>
 15    /// Codec name for MP3.
 16    /// </summary>
 17    public const string MP3 = "mp4a.40.34";
 18
 19    /// <summary>
 20    /// Codec name for AC-3.
 21    /// </summary>
 22    public const string AC3 = "ac-3";
 23
 24    /// <summary>
 25    /// Codec name for E-AC-3.
 26    /// </summary>
 27    public const string EAC3 = "ec-3";
 28
 29    /// <summary>
 30    /// Codec name for FLAC.
 31    /// </summary>
 32    public const string FLAC = "fLaC";
 33
 34    /// <summary>
 35    /// Codec name for ALAC.
 36    /// </summary>
 37    public const string ALAC = "alac";
 38
 39    /// <summary>
 40    /// Codec name for OPUS.
 41    /// </summary>
 42    public const string OPUS = "Opus";
 43
 44    /// <summary>
 45    /// Codec name for TRUEHD.
 46    /// </summary>
 47    public const string TRUEHD = "mlpa";
 48
 49    /// <summary>
 50    /// Gets a MP3 codec string.
 51    /// </summary>
 52    /// <returns>MP3 codec string.</returns>
 53    public static string GetMP3String()
 54    {
 055        return MP3;
 56    }
 57
 58    /// <summary>
 59    /// Gets an AAC codec string.
 60    /// </summary>
 61    /// <param name="profile">AAC profile.</param>
 62    /// <returns>AAC codec string.</returns>
 63    public static string GetAACString(string? profile)
 64    {
 065        StringBuilder result = new StringBuilder("mp4a", 9);
 66
 067        if (string.Equals(profile, "HE-AAC", StringComparison.OrdinalIgnoreCase))
 68        {
 069            result.Append(".40.5");
 70        }
 71        else
 72        {
 73            // Default to LC if profile is invalid
 074            result.Append(".40.2");
 75        }
 76
 077        return result.ToString();
 78    }
 79
 80    /// <summary>
 81    /// Gets an AC-3 codec string.
 82    /// </summary>
 83    /// <returns>AC-3 codec string.</returns>
 84    public static string GetAC3String()
 85    {
 086        return AC3;
 87    }
 88
 89    /// <summary>
 90    /// Gets an E-AC-3 codec string.
 91    /// </summary>
 92    /// <returns>E-AC-3 codec string.</returns>
 93    public static string GetEAC3String()
 94    {
 095        return EAC3;
 96    }
 97
 98    /// <summary>
 99    /// Gets an FLAC codec string.
 100    /// </summary>
 101    /// <returns>FLAC codec string.</returns>
 102    public static string GetFLACString()
 103    {
 0104        return FLAC;
 105    }
 106
 107    /// <summary>
 108    /// Gets an ALAC codec string.
 109    /// </summary>
 110    /// <returns>ALAC codec string.</returns>
 111    public static string GetALACString()
 112    {
 0113        return ALAC;
 114    }
 115
 116    /// <summary>
 117    /// Gets an OPUS codec string.
 118    /// </summary>
 119    /// <returns>OPUS codec string.</returns>
 120    public static string GetOPUSString()
 121    {
 0122        return OPUS;
 123    }
 124
 125    /// <summary>
 126    /// Gets an TRUEHD codec string.
 127    /// </summary>
 128    /// <returns>TRUEHD codec string.</returns>
 129    public static string GetTRUEHDString()
 130    {
 0131        return TRUEHD;
 132    }
 133
 134    /// <summary>
 135    /// Gets an DTS codec string.
 136    /// </summary>
 137    /// <param name="profile">DTS profile.</param>
 138    /// <returns>DTS codec string.</returns>
 139    public static string GetDTSString(string? profile)
 140    {
 0141        if (string.Equals(profile, "DTS", StringComparison.OrdinalIgnoreCase)
 0142            || string.Equals(profile, "DTS-ES", StringComparison.OrdinalIgnoreCase)
 0143            || string.Equals(profile, "DTS 96/24", StringComparison.OrdinalIgnoreCase))
 144        {
 0145            return "dtsc";
 146        }
 147
 0148        if (string.Equals(profile, "DTS-HD HRA", StringComparison.OrdinalIgnoreCase)
 0149            || string.Equals(profile, "DTS-HD MA", StringComparison.OrdinalIgnoreCase)
 0150            || string.Equals(profile, "DTS-HD MA + DTS:X", StringComparison.OrdinalIgnoreCase)
 0151            || string.Equals(profile, "DTS-HD MA + DTS:X IMAX", StringComparison.OrdinalIgnoreCase))
 152        {
 0153            return "dtsh";
 154        }
 155
 0156        if (string.Equals(profile, "DTS Express", StringComparison.OrdinalIgnoreCase))
 157        {
 0158            return "dtse";
 159        }
 160
 161        // Default to DTS core if profile is invalid
 0162        return "dtsc";
 163    }
 164
 165    /// <summary>
 166    /// Gets a H.264 codec string.
 167    /// </summary>
 168    /// <param name="profile">H.264 profile.</param>
 169    /// <param name="level">H.264 level.</param>
 170    /// <returns>H.264 string.</returns>
 171    public static string GetH264String(string? profile, int level)
 172    {
 0173        StringBuilder result = new StringBuilder("avc1", 11);
 174
 0175        if (string.Equals(profile, "high", StringComparison.OrdinalIgnoreCase))
 176        {
 0177            result.Append(".6400");
 178        }
 0179        else if (string.Equals(profile, "main", StringComparison.OrdinalIgnoreCase))
 180        {
 0181            result.Append(".4D40");
 182        }
 0183        else if (string.Equals(profile, "baseline", StringComparison.OrdinalIgnoreCase))
 184        {
 0185            result.Append(".42E0");
 186        }
 187        else
 188        {
 189            // Default to constrained baseline if profile is invalid
 0190            result.Append(".4240");
 191        }
 192
 0193        string levelHex = level.ToString("X2", CultureInfo.InvariantCulture);
 0194        result.Append(levelHex);
 195
 0196        return result.ToString();
 197    }
 198
 199    /// <summary>
 200    /// Gets a H.265 codec string.
 201    /// </summary>
 202    /// <param name="profile">H.265 profile.</param>
 203    /// <param name="level">H.265 level.</param>
 204    /// <returns>H.265 string.</returns>
 205    public static string GetH265String(string? profile, int level)
 206    {
 207        // The h265 syntax is a bit of a mystery at the time this comment was written.
 208        // This is what I've found through various sources:
 209        // FORMAT: [codecTag].[profile].[constraint?].L[level * 30].[UNKNOWN]
 0210        StringBuilder result = new StringBuilder("hvc1", 16);
 211
 0212        if (string.Equals(profile, "main10", StringComparison.OrdinalIgnoreCase)
 0213            || string.Equals(profile, "main 10", StringComparison.OrdinalIgnoreCase))
 214        {
 0215            result.Append(".2.4");
 216        }
 217        else
 218        {
 219            // Default to main if profile is invalid
 0220            result.Append(".1.4");
 221        }
 222
 0223        result.Append(".L")
 0224            .Append(level)
 0225            .Append(".B0");
 226
 0227        return result.ToString();
 228    }
 229
 230    /// <summary>
 231    /// Gets a VP9 codec string.
 232    /// </summary>
 233    /// <param name="width">Video width.</param>
 234    /// <param name="height">Video height.</param>
 235    /// <param name="pixelFormat">Video pixel format.</param>
 236    /// <param name="framerate">Video framerate.</param>
 237    /// <param name="bitDepth">Video bitDepth.</param>
 238    /// <returns>The VP9 codec string.</returns>
 239    public static string GetVp9String(int width, int height, string pixelFormat, float framerate, int bitDepth)
 240    {
 241        // refer: https://www.webmproject.org/vp9/mp4/
 0242        StringBuilder result = new StringBuilder("vp09", 13);
 243
 0244        var profileString = pixelFormat switch
 0245        {
 0246            "yuv420p" => "00",
 0247            "yuvj420p" => "00",
 0248            "yuv422p" => "01",
 0249            "yuv444p" => "01",
 0250            "yuv420p10le" => "02",
 0251            "yuv420p12le" => "02",
 0252            "yuv422p10le" => "03",
 0253            "yuv422p12le" => "03",
 0254            "yuv444p10le" => "03",
 0255            "yuv444p12le" => "03",
 0256            _ => "00"
 0257        };
 258
 0259        var lumaPictureSize = width * height;
 0260        var lumaSampleRate = lumaPictureSize * framerate;
 0261        var levelString = lumaPictureSize switch
 0262        {
 0263            <= 0 => "00",
 0264            <= 36864 => "10",
 0265            <= 73728 => "11",
 0266            <= 122880 => "20",
 0267            <= 245760 => "21",
 0268            <= 552960 => "30",
 0269            <= 983040 => "31",
 0270            <= 2228224 => lumaSampleRate <= 83558400 ? "40" : "41",
 0271            <= 8912896 => lumaSampleRate <= 311951360 ? "50" : (lumaSampleRate <= 588251136 ? "51" : "52"),
 0272            <= 35651584 => lumaSampleRate <= 1176502272 ? "60" : (lumaSampleRate <= 4706009088 ? "61" : "62"),
 0273            _ => "00" // This should not happen
 0274        };
 275
 0276        if (bitDepth != 8
 0277            && bitDepth != 10
 0278            && bitDepth != 12)
 279        {
 280            // Default to 8 bits
 0281            bitDepth = 8;
 282        }
 283
 0284        result.Append('.').Append(profileString).Append('.').Append(levelString);
 0285        var bitDepthD2 = bitDepth.ToString("D2", CultureInfo.InvariantCulture);
 0286        result.Append('.')
 0287            .Append(bitDepthD2);
 288
 0289        return result.ToString();
 290    }
 291
 292    /// <summary>
 293    /// Gets an AV1 codec string.
 294    /// </summary>
 295    /// <param name="profile">AV1 profile.</param>
 296    /// <param name="level">AV1 level.</param>
 297    /// <param name="tierFlag">AV1 tier flag.</param>
 298    /// <param name="bitDepth">AV1 bit depth.</param>
 299    /// <returns>The AV1 codec string.</returns>
 300    public static string GetAv1String(string? profile, int level, bool tierFlag, int bitDepth)
 301    {
 302        // https://aomediacodec.github.io/av1-isobmff/#codecsparam
 303        // FORMAT: [codecTag].[profile].[level][tier].[bitDepth]
 0304        StringBuilder result = new StringBuilder("av01", 13);
 305
 0306        if (string.Equals(profile, "Main", StringComparison.OrdinalIgnoreCase))
 307        {
 0308            result.Append(".0");
 309        }
 0310        else if (string.Equals(profile, "High", StringComparison.OrdinalIgnoreCase))
 311        {
 0312            result.Append(".1");
 313        }
 0314        else if (string.Equals(profile, "Professional", StringComparison.OrdinalIgnoreCase))
 315        {
 0316            result.Append(".2");
 317        }
 318        else
 319        {
 320            // Default to Main
 0321            result.Append(".0");
 322        }
 323
 0324        if (level is <= 0 or > 31)
 325        {
 326            // Default to the maximum defined level 6.3
 0327            level = 19;
 328        }
 329
 0330        if (bitDepth != 8
 0331            && bitDepth != 10
 0332            && bitDepth != 12)
 333        {
 334            // Default to 8 bits
 0335            bitDepth = 8;
 336        }
 337
 0338        result.Append('.')
 0339            // Needed to pad it double digits; otherwise, browsers will reject the stream.
 0340            .AppendFormat(CultureInfo.InvariantCulture, "{0:D2}", level)
 0341            .Append(tierFlag ? 'H' : 'M');
 342
 0343        string bitDepthD2 = bitDepth.ToString("D2", CultureInfo.InvariantCulture);
 0344        result.Append('.')
 0345            .Append(bitDepthD2);
 346
 0347        return result.ToString();
 348    }
 349}