Improve dynamic HDR metadata handling (#13277)

* Add support for bitstream filter to remove dynamic hdr metadata

* Add support for ffprobe's only_first_vframe for HDR10+ detection

* Add BitStreamFilterOptionType for metadata removal check

* Map HDR10+ metadata to VideoRangeType.cs

Current implementation uses a hack that abuses the EL flag to avoid database schema changes. Should add proper field once EFCore migration is merged.

* Add more Dolby Vision Range types

Out of spec ones are problematic and should be marked as a dedicated invalid type and handled by the server to not crash the player.

Profile 7 videos should not be treated as normal HDR10 videos at all and should remove the metadata before serving.

* Remove dynamic hdr metadata when necessary

* Allow direct playback of HDR10+ videos on HDR10 clients

* Only use dovi codec tag when dovi metadata is not removed

* Handle DV Profile 7 Videos better

* Fix HDR10+ with new bitmask

* Indicate the presence of HDR10+ in HLS SUPPLEMENTAL-CODECS

* Fix Dovi 8.4 not labeled as HLG in HLS

* Fallback to dovi_rpu bsf for av1 when possible

* Fix dovi_rpu cli for av1

* Use correct EFCore db column for HDR10+

* Undo outdated migration

* Add proper hdr10+ migration

* Remove outdated migration

* Rebase to new db code

* Add migrations for Hdr10PlusPresentFlag

* Directly use bsf enum

* Add xmldocs for SupportsBitStreamFilterWithOption

* Make `VideoRangeType.Unknown` explicitly default on api models.

* Unset default for non-api model class

* Use tuples for bsf dictionary for now
This commit is contained in:
gnattu 2025-04-03 08:06:02 +08:00 committed by GitHub
parent 9c7cf808aa
commit 49ac705867
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 2328 additions and 67 deletions

View file

@ -30,5 +30,12 @@ namespace MediaBrowser.MediaEncoding.Probing
/// <value>The chapters.</value>
[JsonPropertyName("chapters")]
public IReadOnlyList<MediaChapter> Chapters { get; set; }
/// <summary>
/// Gets or sets the frames.
/// </summary>
/// <value>The streams.</value>
[JsonPropertyName("frames")]
public IReadOnlyList<MediaFrameInfo> Frames { get; set; }
}
}

View file

@ -0,0 +1,184 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace MediaBrowser.MediaEncoding.Probing;
/// <summary>
/// Class MediaFrameInfo.
/// </summary>
public class MediaFrameInfo
{
/// <summary>
/// Gets or sets the media type.
/// </summary>
[JsonPropertyName("media_type")]
public string? MediaType { get; set; }
/// <summary>
/// Gets or sets the StreamIndex.
/// </summary>
[JsonPropertyName("stream_index")]
public int? StreamIndex { get; set; }
/// <summary>
/// Gets or sets the KeyFrame.
/// </summary>
[JsonPropertyName("key_frame")]
public int? KeyFrame { get; set; }
/// <summary>
/// Gets or sets the Pts.
/// </summary>
[JsonPropertyName("pts")]
public long? Pts { get; set; }
/// <summary>
/// Gets or sets the PtsTime.
/// </summary>
[JsonPropertyName("pts_time")]
public string? PtsTime { get; set; }
/// <summary>
/// Gets or sets the BestEffortTimestamp.
/// </summary>
[JsonPropertyName("best_effort_timestamp")]
public long BestEffortTimestamp { get; set; }
/// <summary>
/// Gets or sets the BestEffortTimestampTime.
/// </summary>
[JsonPropertyName("best_effort_timestamp_time")]
public string? BestEffortTimestampTime { get; set; }
/// <summary>
/// Gets or sets the Duration.
/// </summary>
[JsonPropertyName("duration")]
public int Duration { get; set; }
/// <summary>
/// Gets or sets the DurationTime.
/// </summary>
[JsonPropertyName("duration_time")]
public string? DurationTime { get; set; }
/// <summary>
/// Gets or sets the PktPos.
/// </summary>
[JsonPropertyName("pkt_pos")]
public string? PktPos { get; set; }
/// <summary>
/// Gets or sets the PktSize.
/// </summary>
[JsonPropertyName("pkt_size")]
public string? PktSize { get; set; }
/// <summary>
/// Gets or sets the Width.
/// </summary>
[JsonPropertyName("width")]
public int? Width { get; set; }
/// <summary>
/// Gets or sets the Height.
/// </summary>
[JsonPropertyName("height")]
public int? Height { get; set; }
/// <summary>
/// Gets or sets the CropTop.
/// </summary>
[JsonPropertyName("crop_top")]
public int? CropTop { get; set; }
/// <summary>
/// Gets or sets the CropBottom.
/// </summary>
[JsonPropertyName("crop_bottom")]
public int? CropBottom { get; set; }
/// <summary>
/// Gets or sets the CropLeft.
/// </summary>
[JsonPropertyName("crop_left")]
public int? CropLeft { get; set; }
/// <summary>
/// Gets or sets the CropRight.
/// </summary>
[JsonPropertyName("crop_right")]
public int? CropRight { get; set; }
/// <summary>
/// Gets or sets the PixFmt.
/// </summary>
[JsonPropertyName("pix_fmt")]
public string? PixFmt { get; set; }
/// <summary>
/// Gets or sets the SampleAspectRatio.
/// </summary>
[JsonPropertyName("sample_aspect_ratio")]
public string? SampleAspectRatio { get; set; }
/// <summary>
/// Gets or sets the PictType.
/// </summary>
[JsonPropertyName("pict_type")]
public string? PictType { get; set; }
/// <summary>
/// Gets or sets the InterlacedFrame.
/// </summary>
[JsonPropertyName("interlaced_frame")]
public int? InterlacedFrame { get; set; }
/// <summary>
/// Gets or sets the TopFieldFirst.
/// </summary>
[JsonPropertyName("top_field_first")]
public int? TopFieldFirst { get; set; }
/// <summary>
/// Gets or sets the RepeatPict.
/// </summary>
[JsonPropertyName("repeat_pict")]
public int? RepeatPict { get; set; }
/// <summary>
/// Gets or sets the ColorRange.
/// </summary>
[JsonPropertyName("color_range")]
public string? ColorRange { get; set; }
/// <summary>
/// Gets or sets the ColorSpace.
/// </summary>
[JsonPropertyName("color_space")]
public string? ColorSpace { get; set; }
/// <summary>
/// Gets or sets the ColorPrimaries.
/// </summary>
[JsonPropertyName("color_primaries")]
public string? ColorPrimaries { get; set; }
/// <summary>
/// Gets or sets the ColorTransfer.
/// </summary>
[JsonPropertyName("color_transfer")]
public string? ColorTransfer { get; set; }
/// <summary>
/// Gets or sets the ChromaLocation.
/// </summary>
[JsonPropertyName("chroma_location")]
public string? ChromaLocation { get; set; }
/// <summary>
/// Gets or sets the SideDataList.
/// </summary>
[JsonPropertyName("side_data_list")]
public IReadOnlyList<MediaFrameSideDataInfo>? SideDataList { get; set; }
}

View file

@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
namespace MediaBrowser.MediaEncoding.Probing;
/// <summary>
/// Class MediaFrameSideDataInfo.
/// Currently only records the SideDataType for HDR10+ detection.
/// </summary>
public class MediaFrameSideDataInfo
{
/// <summary>
/// Gets or sets the SideDataType.
/// </summary>
[JsonPropertyName("side_data_type")]
public string? SideDataType { get; set; }
}

View file

@ -105,8 +105,9 @@ namespace MediaBrowser.MediaEncoding.Probing
SetSize(data, info);
var internalStreams = data.Streams ?? Array.Empty<MediaStreamInfo>();
var internalFrames = data.Frames ?? Array.Empty<MediaFrameInfo>();
info.MediaStreams = internalStreams.Select(s => GetMediaStream(isAudio, s, data.Format))
info.MediaStreams = internalStreams.Select(s => GetMediaStream(isAudio, s, data.Format, internalFrames))
.Where(i => i is not null)
// Drop subtitle streams if we don't know the codec because it will just cause failures if we don't know how to handle them
.Where(i => i.Type != MediaStreamType.Subtitle || !string.IsNullOrWhiteSpace(i.Codec))
@ -685,8 +686,9 @@ namespace MediaBrowser.MediaEncoding.Probing
/// <param name="isAudio">if set to <c>true</c> [is info].</param>
/// <param name="streamInfo">The stream info.</param>
/// <param name="formatInfo">The format info.</param>
/// <param name="frameInfoList">The frame info.</param>
/// <returns>MediaStream.</returns>
private MediaStream GetMediaStream(bool isAudio, MediaStreamInfo streamInfo, MediaFormatInfo formatInfo)
private MediaStream GetMediaStream(bool isAudio, MediaStreamInfo streamInfo, MediaFormatInfo formatInfo, IReadOnlyList<MediaFrameInfo> frameInfoList)
{
// These are mp4 chapters
if (string.Equals(streamInfo.CodecName, "mov_text", StringComparison.OrdinalIgnoreCase))
@ -904,6 +906,15 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
}
var frameInfo = frameInfoList?.FirstOrDefault(i => i.StreamIndex == stream.Index);
if (frameInfo?.SideDataList != null)
{
if (frameInfo.SideDataList.Any(data => string.Equals(data.SideDataType, "HDR Dynamic Metadata SMPTE2094-40 (HDR10+)", StringComparison.OrdinalIgnoreCase)))
{
stream.Hdr10PlusPresentFlag = true;
}
}
}
else if (streamInfo.CodecType == CodecType.Data)
{