using System; using System.Globalization; using System.Text; namespace Jellyfin.Api.Helpers; /// /// Helpers to generate HLS codec strings according to /// RFC 6381 section 3.3 /// and the MP4 Registration Authority. /// public static class HlsCodecStringHelpers { /// /// Codec name for MP3. /// public const string MP3 = "mp4a.40.34"; /// /// Codec name for AC-3. /// public const string AC3 = "mp4a.a5"; /// /// Codec name for E-AC-3. /// public const string EAC3 = "mp4a.a6"; /// /// Codec name for FLAC. /// public const string FLAC = "fLaC"; /// /// Codec name for ALAC. /// public const string ALAC = "alac"; /// /// Codec name for OPUS. /// public const string OPUS = "Opus"; /// /// Gets a MP3 codec string. /// /// MP3 codec string. public static string GetMP3String() { return MP3; } /// /// Gets an AAC codec string. /// /// AAC profile. /// AAC codec string. public static string GetAACString(string? profile) { StringBuilder result = new StringBuilder("mp4a", 9); if (string.Equals(profile, "HE", StringComparison.OrdinalIgnoreCase)) { result.Append(".40.5"); } else { // Default to LC if profile is invalid result.Append(".40.2"); } return result.ToString(); } /// /// Gets an AC-3 codec string. /// /// AC-3 codec string. public static string GetAC3String() { return AC3; } /// /// Gets an E-AC-3 codec string. /// /// E-AC-3 codec string. public static string GetEAC3String() { return EAC3; } /// /// Gets an FLAC codec string. /// /// FLAC codec string. public static string GetFLACString() { return FLAC; } /// /// Gets an ALAC codec string. /// /// ALAC codec string. public static string GetALACString() { return ALAC; } /// /// Gets an OPUS codec string. /// /// OPUS codec string. public static string GetOPUSString() { return OPUS; } /// /// Gets a H.264 codec string. /// /// H.264 profile. /// H.264 level. /// H.264 string. public static string GetH264String(string? profile, int level) { StringBuilder result = new StringBuilder("avc1", 11); if (string.Equals(profile, "high", StringComparison.OrdinalIgnoreCase)) { result.Append(".6400"); } else if (string.Equals(profile, "main", StringComparison.OrdinalIgnoreCase)) { result.Append(".4D40"); } else if (string.Equals(profile, "baseline", StringComparison.OrdinalIgnoreCase)) { result.Append(".42E0"); } else { // Default to constrained baseline if profile is invalid result.Append(".4240"); } string levelHex = level.ToString("X2", CultureInfo.InvariantCulture); result.Append(levelHex); return result.ToString(); } /// /// Gets a H.265 codec string. /// /// H.265 profile. /// H.265 level. /// H.265 string. public static string GetH265String(string? profile, int level) { // The h265 syntax is a bit of a mystery at the time this comment was written. // This is what I've found through various sources: // FORMAT: [codecTag].[profile].[constraint?].L[level * 30].[UNKNOWN] StringBuilder result = new StringBuilder("hvc1", 16); if (string.Equals(profile, "main10", StringComparison.OrdinalIgnoreCase) || string.Equals(profile, "main 10", StringComparison.OrdinalIgnoreCase)) { result.Append(".2.4"); } else { // Default to main if profile is invalid result.Append(".1.4"); } result.Append(".L") .Append(level) .Append(".B0"); return result.ToString(); } /// /// Gets an AV1 codec string. /// /// AV1 profile. /// AV1 level. /// AV1 tier flag. /// AV1 bit depth. /// The AV1 codec string. public static string GetAv1String(string? profile, int level, bool tierFlag, int bitDepth) { // https://aomediacodec.github.io/av1-isobmff/#codecsparam // FORMAT: [codecTag].[profile].[level][tier].[bitDepth] StringBuilder result = new StringBuilder("av01", 13); if (string.Equals(profile, "Main", StringComparison.OrdinalIgnoreCase)) { result.Append(".0"); } else if (string.Equals(profile, "High", StringComparison.OrdinalIgnoreCase)) { result.Append(".1"); } else if (string.Equals(profile, "Professional", StringComparison.OrdinalIgnoreCase)) { result.Append(".2"); } else { // Default to Main result.Append(".0"); } if (level is <= 0 or > 31) { // Default to the maximum defined level 6.3 level = 19; } if (bitDepth != 8 && bitDepth != 10 && bitDepth != 12) { // Default to 8 bits bitDepth = 8; } result.Append('.') // Needed to pad it double digits; otherwise, browsers will reject the stream. .AppendFormat(CultureInfo.InvariantCulture, "{0:D2}", level) .Append(tierFlag ? 'H' : 'M'); string bitDepthD2 = bitDepth.ToString("D2", CultureInfo.InvariantCulture); result.Append('.') .Append(bitDepthD2); return result.ToString(); } }