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();
}
}