diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index a58da17d59..0e620e72a9 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -304,6 +304,8 @@ public class DynamicHlsHelper AppendPlaylistCodecsField(playlistBuilder, state); + AppendPlaylistSupplementalCodecsField(playlistBuilder, state); + AppendPlaylistResolutionField(playlistBuilder, state); AppendPlaylistFramerateField(playlistBuilder, state); @@ -406,6 +408,48 @@ public class DynamicHlsHelper } } + /// <summary> + /// Appends a SUPPLEMENTAL-CODECS field containing formatted strings of + /// the active streams output Dolby Vision Videos. + /// </summary> + /// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/> + /// <seealso cref="GetPlaylistVideoCodecs(StreamState, string, int)"/> + /// <param name="builder">StringBuilder to append the field to.</param> + /// <param name="state">StreamState of the current stream.</param> + private void AppendPlaylistSupplementalCodecsField(StringBuilder builder, StreamState state) + { + // Dolby Vision currently cannot exist when transcoding + if (!EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) + { + return; + } + + var dvProfile = state.VideoStream.DvProfile; + var dvLevel = state.VideoStream.DvLevel; + var dvRangeString = state.VideoStream.VideoRangeType switch + { + VideoRangeType.DOVIWithHDR10 => "db1p", + VideoRangeType.DOVIWithHLG => "db4h", + _ => string.Empty + }; + + if (dvProfile is null || dvLevel is null || string.IsNullOrEmpty(dvRangeString)) + { + return; + } + + var dvFourCc = string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase) ? "dav1" : "dvh1"; + builder.Append(",SUPPLEMENTAL-CODECS=\"") + .Append(dvFourCc) + .Append('.') + .Append(dvProfile.Value.ToString("D2", CultureInfo.InvariantCulture)) + .Append('.') + .Append(dvLevel.Value.ToString("D2", CultureInfo.InvariantCulture)) + .Append('/') + .Append(dvRangeString) + .Append('"'); + } + /// <summary> /// Appends a RESOLUTION field containing the resolution of the output stream. /// </summary>