mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-04-18 19:25:00 -04:00
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:
parent
9c7cf808aa
commit
49ac705867
21 changed files with 2328 additions and 67 deletions
|
@ -1675,7 +1675,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||||
}
|
}
|
||||||
|
|
||||||
var audioCodec = _encodingHelper.GetAudioEncoder(state);
|
var audioCodec = _encodingHelper.GetAudioEncoder(state);
|
||||||
var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container);
|
var bitStreamArgs = _encodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container);
|
||||||
|
|
||||||
// opus, dts, truehd and flac (in FFmpeg 5 and older) are experimental in mp4 muxer
|
// opus, dts, truehd and flac (in FFmpeg 5 and older) are experimental in mp4 muxer
|
||||||
var strictArgs = string.Empty;
|
var strictArgs = string.Empty;
|
||||||
|
@ -1822,10 +1822,11 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||||
// Clients reporting Dolby Vision capabilities with fallbacks may only support the fallback layer.
|
// Clients reporting Dolby Vision capabilities with fallbacks may only support the fallback layer.
|
||||||
// Only enable Dolby Vision remuxing if the client explicitly declares support for profiles without fallbacks.
|
// Only enable Dolby Vision remuxing if the client explicitly declares support for profiles without fallbacks.
|
||||||
var clientSupportsDoVi = requestedRange.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIgnoreCase);
|
var clientSupportsDoVi = requestedRange.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIgnoreCase);
|
||||||
var videoIsDoVi = state.VideoStream.VideoRangeType is VideoRangeType.DOVI or VideoRangeType.DOVIWithHDR10 or VideoRangeType.DOVIWithHLG or VideoRangeType.DOVIWithSDR;
|
var videoIsDoVi = EncodingHelper.IsDovi(state.VideoStream);
|
||||||
|
|
||||||
if (EncodingHelper.IsCopyCodec(codec)
|
if (EncodingHelper.IsCopyCodec(codec)
|
||||||
&& (videoIsDoVi && clientSupportsDoVi))
|
&& (videoIsDoVi && clientSupportsDoVi)
|
||||||
|
&& !_encodingHelper.IsDoviRemoved(state))
|
||||||
{
|
{
|
||||||
if (isActualOutputVideoCodecHevc)
|
if (isActualOutputVideoCodecHevc)
|
||||||
{
|
{
|
||||||
|
@ -1855,7 +1856,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||||
// If h264_mp4toannexb is ever added, do not use it for live tv.
|
// If h264_mp4toannexb is ever added, do not use it for live tv.
|
||||||
if (state.VideoStream is not null && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
|
if (state.VideoStream is not null && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
string bitStreamArgs = EncodingHelper.GetBitStreamArgs(state.VideoStream);
|
string bitStreamArgs = _encodingHelper.GetBitStreamArgs(state, MediaStreamType.Video);
|
||||||
if (!string.IsNullOrEmpty(bitStreamArgs))
|
if (!string.IsNullOrEmpty(bitStreamArgs))
|
||||||
{
|
{
|
||||||
args += " " + bitStreamArgs;
|
args += " " + bitStreamArgs;
|
||||||
|
|
|
@ -345,13 +345,15 @@ public class DynamicHlsHelper
|
||||||
|
|
||||||
if (videoRange == VideoRange.HDR)
|
if (videoRange == VideoRange.HDR)
|
||||||
{
|
{
|
||||||
if (videoRangeType == VideoRangeType.HLG)
|
switch (videoRangeType)
|
||||||
{
|
{
|
||||||
builder.Append(",VIDEO-RANGE=HLG");
|
case VideoRangeType.HLG:
|
||||||
}
|
case VideoRangeType.DOVIWithHLG:
|
||||||
else
|
builder.Append(",VIDEO-RANGE=HLG");
|
||||||
{
|
break;
|
||||||
builder.Append(",VIDEO-RANGE=PQ");
|
default:
|
||||||
|
builder.Append(",VIDEO-RANGE=PQ");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -418,36 +420,67 @@ public class DynamicHlsHelper
|
||||||
/// <param name="state">StreamState of the current stream.</param>
|
/// <param name="state">StreamState of the current stream.</param>
|
||||||
private void AppendPlaylistSupplementalCodecsField(StringBuilder builder, StreamState state)
|
private void AppendPlaylistSupplementalCodecsField(StringBuilder builder, StreamState state)
|
||||||
{
|
{
|
||||||
// Dolby Vision currently cannot exist when transcoding
|
// HDR dynamic metadata currently cannot exist when transcoding
|
||||||
if (!EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
|
if (!EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var dvProfile = state.VideoStream.DvProfile;
|
if (EncodingHelper.IsDovi(state.VideoStream) && !_encodingHelper.IsDoviRemoved(state))
|
||||||
var dvLevel = state.VideoStream.DvLevel;
|
|
||||||
var dvRangeString = state.VideoStream.VideoRangeType switch
|
|
||||||
{
|
{
|
||||||
VideoRangeType.DOVIWithHDR10 => "db1p",
|
AppendDvString();
|
||||||
VideoRangeType.DOVIWithHLG => "db4h",
|
}
|
||||||
_ => string.Empty
|
else if (EncodingHelper.IsHdr10Plus(state.VideoStream) && !_encodingHelper.IsHdr10PlusRemoved(state))
|
||||||
};
|
|
||||||
|
|
||||||
if (dvProfile is null || dvLevel is null || string.IsNullOrEmpty(dvRangeString))
|
|
||||||
{
|
{
|
||||||
return;
|
AppendHdr10PlusString();
|
||||||
}
|
}
|
||||||
|
|
||||||
var dvFourCc = string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase) ? "dav1" : "dvh1";
|
return;
|
||||||
builder.Append(",SUPPLEMENTAL-CODECS=\"")
|
|
||||||
.Append(dvFourCc)
|
void AppendDvString()
|
||||||
.Append('.')
|
{
|
||||||
.Append(dvProfile.Value.ToString("D2", CultureInfo.InvariantCulture))
|
var dvProfile = state.VideoStream.DvProfile;
|
||||||
.Append('.')
|
var dvLevel = state.VideoStream.DvLevel;
|
||||||
.Append(dvLevel.Value.ToString("D2", CultureInfo.InvariantCulture))
|
var dvRangeString = state.VideoStream.VideoRangeType switch
|
||||||
.Append('/')
|
{
|
||||||
.Append(dvRangeString)
|
VideoRangeType.DOVIWithHDR10 => "db1p",
|
||||||
.Append('"');
|
VideoRangeType.DOVIWithHLG => "db4h",
|
||||||
|
VideoRangeType.DOVIWithHDR10Plus => "db1p", // The HDR10+ metadata would be removed if Dovi metadata is not removed
|
||||||
|
_ => string.Empty // Don't label Dovi with EL and SDR due to compatability issues, ignore invalid configurations
|
||||||
|
};
|
||||||
|
|
||||||
|
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('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendHdr10PlusString()
|
||||||
|
{
|
||||||
|
var videoCodecLevel = GetOutputVideoCodecLevel(state);
|
||||||
|
if (string.IsNullOrEmpty(state.ActualOutputVideoCodec) || videoCodecLevel is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var videoCodecString = GetPlaylistVideoCodecs(state, state.ActualOutputVideoCodec, videoCodecLevel.Value);
|
||||||
|
builder.Append(",SUPPLEMENTAL-CODECS=\"")
|
||||||
|
.Append(videoCodecString)
|
||||||
|
.Append('/')
|
||||||
|
.Append("cdm4")
|
||||||
|
.Append('"');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -45,6 +45,27 @@ public enum VideoRangeType
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DOVIWithSDR,
|
DOVIWithSDR,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dolby Vision with Enhancment Layer (Profile 7).
|
||||||
|
/// </summary>
|
||||||
|
DOVIWithEL,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dolby Vision and HDR10+ Metadata coexists.
|
||||||
|
/// </summary>
|
||||||
|
DOVIWithHDR10Plus,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dolby Vision with Enhancment Layer (Profile 7) and HDR10+ Metadata coexists.
|
||||||
|
/// </summary>
|
||||||
|
DOVIWithELHDR10Plus,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dolby Vision with invalid configuration. e.g. Profile 8 compat id 6.
|
||||||
|
/// When using this range, the server would assume the video is still HDR10 after removing the Dolby Vision metadata.
|
||||||
|
/// </summary>
|
||||||
|
DOVIInvalid,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// HDR10+ video range type (10bit to 16bit).
|
/// HDR10+ video range type (10bit to 16bit).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -140,6 +140,7 @@ public class MediaStreamRepository : IMediaStreamRepository
|
||||||
dto.DvBlSignalCompatibilityId = entity.DvBlSignalCompatibilityId;
|
dto.DvBlSignalCompatibilityId = entity.DvBlSignalCompatibilityId;
|
||||||
dto.IsHearingImpaired = entity.IsHearingImpaired.GetValueOrDefault();
|
dto.IsHearingImpaired = entity.IsHearingImpaired.GetValueOrDefault();
|
||||||
dto.Rotation = entity.Rotation;
|
dto.Rotation = entity.Rotation;
|
||||||
|
dto.Hdr10PlusPresentFlag = entity.Hdr10PlusPresentFlag;
|
||||||
|
|
||||||
if (dto.Type is MediaStreamType.Audio or MediaStreamType.Subtitle)
|
if (dto.Type is MediaStreamType.Audio or MediaStreamType.Subtitle)
|
||||||
{
|
{
|
||||||
|
@ -207,7 +208,8 @@ public class MediaStreamRepository : IMediaStreamRepository
|
||||||
BlPresentFlag = dto.BlPresentFlag,
|
BlPresentFlag = dto.BlPresentFlag,
|
||||||
DvBlSignalCompatibilityId = dto.DvBlSignalCompatibilityId,
|
DvBlSignalCompatibilityId = dto.DvBlSignalCompatibilityId,
|
||||||
IsHearingImpaired = dto.IsHearingImpaired,
|
IsHearingImpaired = dto.IsHearingImpaired,
|
||||||
Rotation = dto.Rotation
|
Rotation = dto.Rotation,
|
||||||
|
Hdr10PlusPresentFlag = dto.Hdr10PlusPresentFlag,
|
||||||
};
|
};
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
namespace MediaBrowser.Controller.MediaEncoding;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enum BitStreamFilterOptionType.
|
||||||
|
/// </summary>
|
||||||
|
public enum BitStreamFilterOptionType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// hevc_metadata bsf with remove_dovi option.
|
||||||
|
/// </summary>
|
||||||
|
HevcMetadataRemoveDovi = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// hevc_metadata bsf with remove_hdr10plus option.
|
||||||
|
/// </summary>
|
||||||
|
HevcMetadataRemoveHdr10Plus = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// av1_metadata bsf with remove_dovi option.
|
||||||
|
/// </summary>
|
||||||
|
Av1MetadataRemoveDovi = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// av1_metadata bsf with remove_hdr10plus option.
|
||||||
|
/// </summary>
|
||||||
|
Av1MetadataRemoveHdr10Plus = 3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// dovi_rpu bsf with strip option.
|
||||||
|
/// </summary>
|
||||||
|
DoviRpuStrip = 4,
|
||||||
|
}
|
|
@ -162,6 +162,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
_configurationManager = configurationManager;
|
_configurationManager = configurationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum DynamicHdrMetadataRemovalPlan
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
RemoveDovi,
|
||||||
|
RemoveHdr10Plus,
|
||||||
|
}
|
||||||
|
|
||||||
[GeneratedRegex(@"\s+")]
|
[GeneratedRegex(@"\s+")]
|
||||||
private static partial Regex WhiteSpaceRegex();
|
private static partial Regex WhiteSpaceRegex();
|
||||||
|
|
||||||
|
@ -342,11 +349,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder || isVideoToolBoxDecoder;
|
return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder || isVideoToolBoxDecoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.VideoStream.VideoRange == VideoRange.HDR
|
// GPU tonemapping supports all HDR RangeTypes
|
||||||
&& (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
|
return state.VideoStream.VideoRange == VideoRange.HDR;
|
||||||
|| state.VideoStream.VideoRangeType == VideoRangeType.HLG
|
|
||||||
|| state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10
|
|
||||||
|| state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithHLG);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsVulkanHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
|
private bool IsVulkanHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
|
||||||
|
@ -381,8 +385,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.VideoStream.VideoRange == VideoRange.HDR
|
return state.VideoStream.VideoRange == VideoRange.HDR
|
||||||
&& (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
|
&& IsDoviWithHdr10Bl(state.VideoStream);
|
||||||
|| state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsVideoToolboxTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
|
private bool IsVideoToolboxTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
|
||||||
|
@ -397,7 +400,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
// Certain DV profile 5 video works in Safari with direct playing, but the VideoToolBox does not produce correct mapping results with transcoding.
|
// Certain DV profile 5 video works in Safari with direct playing, but the VideoToolBox does not produce correct mapping results with transcoding.
|
||||||
// All other HDR formats working.
|
// All other HDR formats working.
|
||||||
return state.VideoStream.VideoRange == VideoRange.HDR
|
return state.VideoStream.VideoRange == VideoRange.HDR
|
||||||
&& state.VideoStream.VideoRangeType is VideoRangeType.HDR10 or VideoRangeType.HLG or VideoRangeType.HDR10Plus or VideoRangeType.DOVIWithHDR10 or VideoRangeType.DOVIWithHLG;
|
&& (IsDoviWithHdr10Bl(state.VideoStream)
|
||||||
|
|| state.VideoStream.VideoRangeType is VideoRangeType.HLG);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsVideoStreamHevcRext(EncodingJobInfo state)
|
private bool IsVideoStreamHevcRext(EncodingJobInfo state)
|
||||||
|
@ -1301,6 +1305,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
|| codec.Contains("hevc", StringComparison.OrdinalIgnoreCase);
|
|| codec.Contains("hevc", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsAv1(MediaStream stream)
|
||||||
|
{
|
||||||
|
var codec = stream.Codec ?? string.Empty;
|
||||||
|
|
||||||
|
return codec.Contains("av1", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsAAC(MediaStream stream)
|
public static bool IsAAC(MediaStream stream)
|
||||||
{
|
{
|
||||||
var codec = stream.Codec ?? string.Empty;
|
var codec = stream.Codec ?? string.Empty;
|
||||||
|
@ -1308,8 +1319,125 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
return codec.Contains("aac", StringComparison.OrdinalIgnoreCase);
|
return codec.Contains("aac", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetBitStreamArgs(MediaStream stream)
|
public static bool IsDoviWithHdr10Bl(MediaStream stream)
|
||||||
{
|
{
|
||||||
|
var rangeType = stream?.VideoRangeType;
|
||||||
|
|
||||||
|
return rangeType is VideoRangeType.DOVIWithHDR10
|
||||||
|
or VideoRangeType.DOVIWithEL
|
||||||
|
or VideoRangeType.DOVIWithHDR10Plus
|
||||||
|
or VideoRangeType.DOVIWithELHDR10Plus
|
||||||
|
or VideoRangeType.DOVIInvalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsDovi(MediaStream stream)
|
||||||
|
{
|
||||||
|
var rangeType = stream?.VideoRangeType;
|
||||||
|
|
||||||
|
return IsDoviWithHdr10Bl(stream)
|
||||||
|
|| (rangeType is VideoRangeType.DOVI
|
||||||
|
or VideoRangeType.DOVIWithHLG
|
||||||
|
or VideoRangeType.DOVIWithSDR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsHdr10Plus(MediaStream stream)
|
||||||
|
{
|
||||||
|
var rangeType = stream?.VideoRangeType;
|
||||||
|
|
||||||
|
return rangeType is VideoRangeType.HDR10Plus
|
||||||
|
or VideoRangeType.DOVIWithHDR10Plus
|
||||||
|
or VideoRangeType.DOVIWithELHDR10Plus;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if dynamic HDR metadata should be removed during stream copy.
|
||||||
|
/// Please note this check assumes the range check has already been done
|
||||||
|
/// and trivial fallbacks like HDR10+ to HDR10, DOVIWithHDR10 to HDR10 is already checked.
|
||||||
|
/// </summary>
|
||||||
|
private static DynamicHdrMetadataRemovalPlan ShouldRemoveDynamicHdrMetadata(EncodingJobInfo state)
|
||||||
|
{
|
||||||
|
var videoStream = state.VideoStream;
|
||||||
|
if (videoStream.VideoRange is not VideoRange.HDR)
|
||||||
|
{
|
||||||
|
return DynamicHdrMetadataRemovalPlan.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
var requestedRangeTypes = state.GetRequestedRangeTypes(state.VideoStream.Codec);
|
||||||
|
if (requestedRangeTypes.Length == 0)
|
||||||
|
{
|
||||||
|
return DynamicHdrMetadataRemovalPlan.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.OrdinalIgnoreCase);
|
||||||
|
var requestHasDOVI = requestedRangeTypes.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIgnoreCase);
|
||||||
|
var requestHasDOVIwithEL = requestedRangeTypes.Contains(VideoRangeType.DOVIWithEL.ToString(), StringComparison.OrdinalIgnoreCase);
|
||||||
|
var requestHasDOVIwithELHDR10plus = requestedRangeTypes.Contains(VideoRangeType.DOVIWithELHDR10Plus.ToString(), StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var shouldRemoveHdr10Plus = false;
|
||||||
|
// Case 1: Client supports HDR10, does not support DOVI with EL but EL presets
|
||||||
|
var shouldRemoveDovi = (!requestHasDOVIwithEL && requestHasHDR10) && videoStream.VideoRangeType == VideoRangeType.DOVIWithEL;
|
||||||
|
|
||||||
|
// Case 2: Client supports DOVI, does not support broken DOVI config
|
||||||
|
// Client does not report DOVI support should be allowed to copy bad data for remuxing as HDR10 players would not crash
|
||||||
|
shouldRemoveDovi = shouldRemoveDovi || (requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVIInvalid);
|
||||||
|
|
||||||
|
// Special case: we have a video with both EL and HDR10+
|
||||||
|
// If the client supports EL but not in the case of coexistence with HDR10+, remove HDR10+ for compatibility reasons.
|
||||||
|
// Otherwise, remove DOVI if the client is not a DOVI player
|
||||||
|
if (videoStream.VideoRangeType == VideoRangeType.DOVIWithELHDR10Plus)
|
||||||
|
{
|
||||||
|
shouldRemoveHdr10Plus = requestHasDOVIwithEL && !requestHasDOVIwithELHDR10plus;
|
||||||
|
shouldRemoveDovi = shouldRemoveDovi || !shouldRemoveHdr10Plus;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldRemoveDovi)
|
||||||
|
{
|
||||||
|
return DynamicHdrMetadataRemovalPlan.RemoveDovi;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the client is a Dolby Vision Player, remove the HDR10+ metadata to avoid playback issues
|
||||||
|
shouldRemoveHdr10Plus = shouldRemoveHdr10Plus || (requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10Plus);
|
||||||
|
return shouldRemoveHdr10Plus ? DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus : DynamicHdrMetadataRemovalPlan.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan plan, MediaStream videoStream)
|
||||||
|
{
|
||||||
|
return plan switch
|
||||||
|
{
|
||||||
|
DynamicHdrMetadataRemovalPlan.RemoveDovi => _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.DoviRpuStrip)
|
||||||
|
|| (IsH265(videoStream) && _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.HevcMetadataRemoveDovi))
|
||||||
|
|| (IsAv1(videoStream) && _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.Av1MetadataRemoveDovi)),
|
||||||
|
DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus => (IsH265(videoStream) && _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.HevcMetadataRemoveHdr10Plus))
|
||||||
|
|| (IsAv1(videoStream) && _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.Av1MetadataRemoveHdr10Plus)),
|
||||||
|
_ => true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsDoviRemoved(EncodingJobInfo state)
|
||||||
|
{
|
||||||
|
return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalPlan.RemoveDovi
|
||||||
|
&& CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.RemoveDovi, state.VideoStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsHdr10PlusRemoved(EncodingJobInfo state)
|
||||||
|
{
|
||||||
|
return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus
|
||||||
|
&& CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus, state.VideoStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetBitStreamArgs(EncodingJobInfo state, MediaStreamType streamType)
|
||||||
|
{
|
||||||
|
if (state is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var stream = streamType switch
|
||||||
|
{
|
||||||
|
MediaStreamType.Audio => state.AudioStream,
|
||||||
|
MediaStreamType.Video => state.VideoStream,
|
||||||
|
_ => state.VideoStream
|
||||||
|
};
|
||||||
// TODO This is auto inserted into the mpegts mux so it might not be needed.
|
// TODO This is auto inserted into the mpegts mux so it might not be needed.
|
||||||
// https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb
|
// https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb
|
||||||
if (IsH264(stream))
|
if (IsH264(stream))
|
||||||
|
@ -1317,21 +1445,57 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
return "-bsf:v h264_mp4toannexb";
|
return "-bsf:v h264_mp4toannexb";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsH265(stream))
|
|
||||||
{
|
|
||||||
return "-bsf:v hevc_mp4toannexb";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsAAC(stream))
|
if (IsAAC(stream))
|
||||||
{
|
{
|
||||||
// Convert adts header(mpegts) to asc header(mp4).
|
// Convert adts header(mpegts) to asc header(mp4).
|
||||||
return "-bsf:a aac_adtstoasc";
|
return "-bsf:a aac_adtstoasc";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsH265(stream))
|
||||||
|
{
|
||||||
|
var filter = "-bsf:v hevc_mp4toannexb";
|
||||||
|
|
||||||
|
// The following checks are not complete because the copy would be rejected
|
||||||
|
// if the encoder cannot remove required metadata.
|
||||||
|
// And if bsf is used, we must already be using copy codec.
|
||||||
|
switch (ShouldRemoveDynamicHdrMetadata(state))
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
case DynamicHdrMetadataRemovalPlan.None:
|
||||||
|
break;
|
||||||
|
case DynamicHdrMetadataRemovalPlan.RemoveDovi:
|
||||||
|
filter += _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.HevcMetadataRemoveDovi)
|
||||||
|
? ",hevc_metadata=remove_dovi=1"
|
||||||
|
: ",dovi_rpu=strip=1";
|
||||||
|
break;
|
||||||
|
case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
|
||||||
|
filter += ",hevc_metadata=remove_hdr10plus=1";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsAv1(stream))
|
||||||
|
{
|
||||||
|
switch (ShouldRemoveDynamicHdrMetadata(state))
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
case DynamicHdrMetadataRemovalPlan.None:
|
||||||
|
return null;
|
||||||
|
case DynamicHdrMetadataRemovalPlan.RemoveDovi:
|
||||||
|
return _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.Av1MetadataRemoveDovi)
|
||||||
|
? "-bsf:v av1_metadata=remove_dovi=1"
|
||||||
|
: "-bsf:v dovi_rpu=strip=1";
|
||||||
|
case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
|
||||||
|
return "-bsf:v av1_metadata=remove_hdr10plus=1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceContainer)
|
public string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceContainer)
|
||||||
{
|
{
|
||||||
var bitStreamArgs = string.Empty;
|
var bitStreamArgs = string.Empty;
|
||||||
var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
|
var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
|
||||||
|
@ -1342,7 +1506,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
|| string.Equals(mediaSourceContainer, "aac", StringComparison.OrdinalIgnoreCase)
|
|| string.Equals(mediaSourceContainer, "aac", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase)))
|
|| string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
bitStreamArgs = GetBitStreamArgs(state.AudioStream);
|
bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Audio);
|
||||||
bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs;
|
bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2169,7 +2333,6 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
}
|
}
|
||||||
|
|
||||||
// DOVIWithHDR10 should be compatible with HDR10 supporting players. Same goes with HLG and of course SDR. So allow copy of those formats
|
// DOVIWithHDR10 should be compatible with HDR10 supporting players. Same goes with HLG and of course SDR. So allow copy of those formats
|
||||||
|
|
||||||
var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.OrdinalIgnoreCase);
|
var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.OrdinalIgnoreCase);
|
||||||
var requestHasHLG = requestedRangeTypes.Contains(VideoRangeType.HLG.ToString(), StringComparison.OrdinalIgnoreCase);
|
var requestHasHLG = requestedRangeTypes.Contains(VideoRangeType.HLG.ToString(), StringComparison.OrdinalIgnoreCase);
|
||||||
var requestHasSDR = requestedRangeTypes.Contains(VideoRangeType.SDR.ToString(), StringComparison.OrdinalIgnoreCase);
|
var requestHasSDR = requestedRangeTypes.Contains(VideoRangeType.SDR.ToString(), StringComparison.OrdinalIgnoreCase);
|
||||||
|
@ -2177,9 +2340,17 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
if (!requestedRangeTypes.Contains(videoStream.VideoRangeType.ToString(), StringComparison.OrdinalIgnoreCase)
|
if (!requestedRangeTypes.Contains(videoStream.VideoRangeType.ToString(), StringComparison.OrdinalIgnoreCase)
|
||||||
&& !((requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10)
|
&& !((requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10)
|
||||||
|| (requestHasHLG && videoStream.VideoRangeType == VideoRangeType.DOVIWithHLG)
|
|| (requestHasHLG && videoStream.VideoRangeType == VideoRangeType.DOVIWithHLG)
|
||||||
|| (requestHasSDR && videoStream.VideoRangeType == VideoRangeType.DOVIWithSDR)))
|
|| (requestHasSDR && videoStream.VideoRangeType == VideoRangeType.DOVIWithSDR)
|
||||||
|
|| (requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.HDR10Plus)))
|
||||||
{
|
{
|
||||||
return false;
|
// Check complicated cases where we need to remove dynamic metadata
|
||||||
|
// Conservatively refuse to copy if the encoder can't remove dynamic metadata,
|
||||||
|
// but a removal is required for compatability reasons.
|
||||||
|
var dynamicHdrMetadataRemovalPlan = ShouldRemoveDynamicHdrMetadata(state);
|
||||||
|
if (!CanEncoderRemoveDynamicHdrMetadata(dynamicHdrMetadataRemovalPlan, videoStream))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7244,7 +7415,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
&& string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
|
&& string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
|
||||||
&& !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
|
&& !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
string bitStreamArgs = GetBitStreamArgs(state.VideoStream);
|
string bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Video);
|
||||||
if (!string.IsNullOrEmpty(bitStreamArgs))
|
if (!string.IsNullOrEmpty(bitStreamArgs))
|
||||||
{
|
{
|
||||||
args += " " + bitStreamArgs;
|
args += " " + bitStreamArgs;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
|
|
|
@ -116,6 +116,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
/// <returns><c>true</c> if the filter is supported, <c>false</c> otherwise.</returns>
|
/// <returns><c>true</c> if the filter is supported, <c>false</c> otherwise.</returns>
|
||||||
bool SupportsFilterWithOption(FilterOptionType option);
|
bool SupportsFilterWithOption(FilterOptionType option);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the bitstream filter is supported with the given option.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="option">The option.</param>
|
||||||
|
/// <returns><c>true</c> if the bitstream filter is supported, <c>false</c> otherwise.</returns>
|
||||||
|
bool SupportsBitStreamFilterWithOption(BitStreamFilterOptionType option);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extracts the audio image.
|
/// Extracts the audio image.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -7,6 +7,7 @@ using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MediaBrowser.MediaEncoding.Encoder
|
namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
@ -160,6 +161,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
{ 6, new string[] { "transpose_opencl", "rotate by half-turn" } }
|
{ 6, new string[] { "transpose_opencl", "rotate by half-turn" } }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static readonly Dictionary<BitStreamFilterOptionType, (string, string)> _bsfOptionsDict = new Dictionary<BitStreamFilterOptionType, (string, string)>
|
||||||
|
{
|
||||||
|
{ BitStreamFilterOptionType.HevcMetadataRemoveDovi, ("hevc_metadata", "remove_dovi") },
|
||||||
|
{ BitStreamFilterOptionType.HevcMetadataRemoveHdr10Plus, ("hevc_metadata", "remove_hdr10plus") },
|
||||||
|
{ BitStreamFilterOptionType.Av1MetadataRemoveDovi, ("av1_metadata", "remove_dovi") },
|
||||||
|
{ BitStreamFilterOptionType.Av1MetadataRemoveHdr10Plus, ("av1_metadata", "remove_hdr10plus") },
|
||||||
|
{ BitStreamFilterOptionType.DoviRpuStrip, ("dovi_rpu", "strip") }
|
||||||
|
};
|
||||||
|
|
||||||
// These are the library versions that corresponds to our minimum ffmpeg version 4.4 according to the version table below
|
// These are the library versions that corresponds to our minimum ffmpeg version 4.4 according to the version table below
|
||||||
// Refers to the versions in https://ffmpeg.org/download.html
|
// Refers to the versions in https://ffmpeg.org/download.html
|
||||||
private static readonly Dictionary<string, Version> _ffmpegMinimumLibraryVersions = new Dictionary<string, Version>
|
private static readonly Dictionary<string, Version> _ffmpegMinimumLibraryVersions = new Dictionary<string, Version>
|
||||||
|
@ -286,6 +296,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
|
||||||
public IDictionary<int, bool> GetFiltersWithOption() => GetFFmpegFiltersWithOption();
|
public IDictionary<int, bool> GetFiltersWithOption() => GetFFmpegFiltersWithOption();
|
||||||
|
|
||||||
|
public IDictionary<BitStreamFilterOptionType, bool> GetBitStreamFiltersWithOption() => _bsfOptionsDict
|
||||||
|
.ToDictionary(item => item.Key, item => CheckBitStreamFilterWithOption(item.Value.Item1, item.Value.Item2));
|
||||||
|
|
||||||
public Version? GetFFmpegVersion()
|
public Version? GetFFmpegVersion()
|
||||||
{
|
{
|
||||||
string output;
|
string output;
|
||||||
|
@ -495,6 +508,34 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CheckBitStreamFilterWithOption(string filter, string option)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(filter) || string.IsNullOrEmpty(option))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string output;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
output = GetProcessOutput(_encoderPath, "-h bsf=" + filter, false, null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error detecting the given bit stream filter");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.Contains("Bit stream filter " + filter, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return output.Contains(option, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogWarning("Bit stream filter: {Name} with option {Option} is not available", filter, option);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public bool CheckSupportedRuntimeKey(string keyDesc, Version? ffmpegVersion)
|
public bool CheckSupportedRuntimeKey(string keyDesc, Version? ffmpegVersion)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(keyDesc))
|
if (string.IsNullOrEmpty(keyDesc))
|
||||||
|
@ -523,6 +564,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
return !string.IsNullOrEmpty(flag) && GetProcessExitCode(_encoderPath, $"-loglevel quiet -hwaccel_flags +{flag} -hide_banner -f lavfi -i nullsrc=s=1x1:d=100 -f null -");
|
return !string.IsNullOrEmpty(flag) && GetProcessExitCode(_encoderPath, $"-loglevel quiet -hwaccel_flags +{flag} -hide_banner -f lavfi -i nullsrc=s=1x1:d=100 -f null -");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CheckSupportedProberOption(string option, string proberPath)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrEmpty(option) && GetProcessExitCode(proberPath, $"-loglevel quiet -f lavfi -i nullsrc=s=1x1:d=1 -{option}");
|
||||||
|
}
|
||||||
|
|
||||||
private IEnumerable<string> GetCodecs(Codec codec)
|
private IEnumerable<string> GetCodecs(Codec codec)
|
||||||
{
|
{
|
||||||
string codecstr = codec == Codec.Encoder ? "encoders" : "decoders";
|
string codecstr = codec == Codec.Encoder ? "encoders" : "decoders";
|
||||||
|
|
|
@ -73,9 +73,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
private List<string> _hwaccels = new List<string>();
|
private List<string> _hwaccels = new List<string>();
|
||||||
private List<string> _filters = new List<string>();
|
private List<string> _filters = new List<string>();
|
||||||
private IDictionary<int, bool> _filtersWithOption = new Dictionary<int, bool>();
|
private IDictionary<int, bool> _filtersWithOption = new Dictionary<int, bool>();
|
||||||
|
private IDictionary<BitStreamFilterOptionType, bool> _bitStreamFiltersWithOption = new Dictionary<BitStreamFilterOptionType, bool>();
|
||||||
|
|
||||||
private bool _isPkeyPauseSupported = false;
|
private bool _isPkeyPauseSupported = false;
|
||||||
private bool _isLowPriorityHwDecodeSupported = false;
|
private bool _isLowPriorityHwDecodeSupported = false;
|
||||||
|
private bool _proberSupportsFirstVideoFrame = false;
|
||||||
|
|
||||||
private bool _isVaapiDeviceAmd = false;
|
private bool _isVaapiDeviceAmd = false;
|
||||||
private bool _isVaapiDeviceInteliHD = false;
|
private bool _isVaapiDeviceInteliHD = false;
|
||||||
|
@ -222,6 +224,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
SetAvailableEncoders(validator.GetEncoders());
|
SetAvailableEncoders(validator.GetEncoders());
|
||||||
SetAvailableFilters(validator.GetFilters());
|
SetAvailableFilters(validator.GetFilters());
|
||||||
SetAvailableFiltersWithOption(validator.GetFiltersWithOption());
|
SetAvailableFiltersWithOption(validator.GetFiltersWithOption());
|
||||||
|
SetAvailableBitStreamFiltersWithOption(validator.GetBitStreamFiltersWithOption());
|
||||||
SetAvailableHwaccels(validator.GetHwaccels());
|
SetAvailableHwaccels(validator.GetHwaccels());
|
||||||
SetMediaEncoderVersion(validator);
|
SetMediaEncoderVersion(validator);
|
||||||
|
|
||||||
|
@ -229,6 +232,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
|
||||||
_isPkeyPauseSupported = validator.CheckSupportedRuntimeKey("p pause transcoding", _ffmpegVersion);
|
_isPkeyPauseSupported = validator.CheckSupportedRuntimeKey("p pause transcoding", _ffmpegVersion);
|
||||||
_isLowPriorityHwDecodeSupported = validator.CheckSupportedHwaccelFlag("low_priority");
|
_isLowPriorityHwDecodeSupported = validator.CheckSupportedHwaccelFlag("low_priority");
|
||||||
|
_proberSupportsFirstVideoFrame = validator.CheckSupportedProberOption("only_first_vframe", _ffprobePath);
|
||||||
|
|
||||||
// Check the Vaapi device vendor
|
// Check the Vaapi device vendor
|
||||||
if (OperatingSystem.IsLinux()
|
if (OperatingSystem.IsLinux()
|
||||||
|
@ -342,6 +346,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
_filtersWithOption = dict;
|
_filtersWithOption = dict;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetAvailableBitStreamFiltersWithOption(IDictionary<BitStreamFilterOptionType, bool> dict)
|
||||||
|
{
|
||||||
|
_bitStreamFiltersWithOption = dict;
|
||||||
|
}
|
||||||
|
|
||||||
public void SetMediaEncoderVersion(EncoderValidator validator)
|
public void SetMediaEncoderVersion(EncoderValidator validator)
|
||||||
{
|
{
|
||||||
_ffmpegVersion = validator.GetFFmpegVersion();
|
_ffmpegVersion = validator.GetFFmpegVersion();
|
||||||
|
@ -382,6 +391,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool SupportsBitStreamFilterWithOption(BitStreamFilterOptionType option)
|
||||||
|
{
|
||||||
|
return _bitStreamFiltersWithOption.TryGetValue(option, out var val) && val;
|
||||||
|
}
|
||||||
|
|
||||||
public bool CanEncodeToAudioCodec(string codec)
|
public bool CanEncodeToAudioCodec(string codec)
|
||||||
{
|
{
|
||||||
if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
|
||||||
|
@ -501,6 +515,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
var args = extractChapters
|
var args = extractChapters
|
||||||
? "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_chapters -show_format"
|
? "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_chapters -show_format"
|
||||||
: "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_format";
|
: "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_format";
|
||||||
|
|
||||||
|
if (_proberSupportsFirstVideoFrame)
|
||||||
|
{
|
||||||
|
args += " -show_frames -only_first_vframe";
|
||||||
|
}
|
||||||
|
|
||||||
args = string.Format(CultureInfo.InvariantCulture, args, probeSizeArgument, inputPath, _threads).Trim();
|
args = string.Format(CultureInfo.InvariantCulture, args, probeSizeArgument, inputPath, _threads).Trim();
|
||||||
|
|
||||||
var process = new Process
|
var process = new Process
|
||||||
|
|
|
@ -30,5 +30,12 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||||
/// <value>The chapters.</value>
|
/// <value>The chapters.</value>
|
||||||
[JsonPropertyName("chapters")]
|
[JsonPropertyName("chapters")]
|
||||||
public IReadOnlyList<MediaChapter> Chapters { get; set; }
|
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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
184
MediaBrowser.MediaEncoding/Probing/MediaFrameInfo.cs
Normal file
184
MediaBrowser.MediaEncoding/Probing/MediaFrameInfo.cs
Normal 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; }
|
||||||
|
}
|
16
MediaBrowser.MediaEncoding/Probing/MediaFrameSideDataInfo.cs
Normal file
16
MediaBrowser.MediaEncoding/Probing/MediaFrameSideDataInfo.cs
Normal 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; }
|
||||||
|
}
|
|
@ -105,8 +105,9 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||||
SetSize(data, info);
|
SetSize(data, info);
|
||||||
|
|
||||||
var internalStreams = data.Streams ?? Array.Empty<MediaStreamInfo>();
|
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)
|
.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
|
// 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))
|
.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="isAudio">if set to <c>true</c> [is info].</param>
|
||||||
/// <param name="streamInfo">The stream info.</param>
|
/// <param name="streamInfo">The stream info.</param>
|
||||||
/// <param name="formatInfo">The format info.</param>
|
/// <param name="formatInfo">The format info.</param>
|
||||||
|
/// <param name="frameInfoList">The frame info.</param>
|
||||||
/// <returns>MediaStream.</returns>
|
/// <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
|
// These are mp4 chapters
|
||||||
if (string.Equals(streamInfo.CodecName, "mov_text", StringComparison.OrdinalIgnoreCase))
|
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)
|
else if (streamInfo.CodecType == CodecType.Data)
|
||||||
{
|
{
|
||||||
|
|
|
@ -345,6 +345,15 @@ namespace MediaBrowser.Model.Dlna
|
||||||
return !condition.IsRequired;
|
return !condition.IsRequired;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special case: HDR10 also satisfies if the video is HDR10Plus
|
||||||
|
if (currentValue.Value == VideoRangeType.HDR10Plus)
|
||||||
|
{
|
||||||
|
if (IsConditionSatisfied(condition, VideoRangeType.HDR10))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var conditionType = condition.Condition;
|
var conditionType = condition.Condition;
|
||||||
if (conditionType == ProfileConditionType.EqualsAny)
|
if (conditionType == ProfileConditionType.EqualsAny)
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
|
@ -153,6 +153,8 @@ namespace MediaBrowser.Model.Entities
|
||||||
/// <value>The title.</value>
|
/// <value>The title.</value>
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
public bool? Hdr10PlusPresentFlag { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the video range.
|
/// Gets the video range.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -172,6 +174,7 @@ namespace MediaBrowser.Model.Entities
|
||||||
/// Gets the video range type.
|
/// Gets the video range type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The video range type.</value>
|
/// <value>The video range type.</value>
|
||||||
|
[DefaultValue(VideoRangeType.Unknown)]
|
||||||
public VideoRangeType VideoRangeType
|
public VideoRangeType VideoRangeType
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -779,8 +782,8 @@ namespace MediaBrowser.Model.Entities
|
||||||
var blPresentFlag = BlPresentFlag == 1;
|
var blPresentFlag = BlPresentFlag == 1;
|
||||||
var dvBlCompatId = DvBlSignalCompatibilityId;
|
var dvBlCompatId = DvBlSignalCompatibilityId;
|
||||||
|
|
||||||
var isDoViProfile = dvProfile == 5 || dvProfile == 7 || dvProfile == 8 || dvProfile == 10;
|
var isDoViProfile = dvProfile is 5 or 7 or 8 or 10;
|
||||||
var isDoViFlag = rpuPresentFlag && blPresentFlag && (dvBlCompatId == 0 || dvBlCompatId == 1 || dvBlCompatId == 4 || dvBlCompatId == 2 || dvBlCompatId == 6);
|
var isDoViFlag = rpuPresentFlag && blPresentFlag && dvBlCompatId is 0 or 1 or 4 or 2 or 6;
|
||||||
|
|
||||||
if ((isDoViProfile && isDoViFlag)
|
if ((isDoViProfile && isDoViFlag)
|
||||||
|| string.Equals(codecTag, "dovi", StringComparison.OrdinalIgnoreCase)
|
|| string.Equals(codecTag, "dovi", StringComparison.OrdinalIgnoreCase)
|
||||||
|
@ -788,7 +791,7 @@ namespace MediaBrowser.Model.Entities
|
||||||
|| string.Equals(codecTag, "dvhe", StringComparison.OrdinalIgnoreCase)
|
|| string.Equals(codecTag, "dvhe", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(codecTag, "dav1", StringComparison.OrdinalIgnoreCase))
|
|| string.Equals(codecTag, "dav1", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return dvProfile switch
|
var dvRangeSet = dvProfile switch
|
||||||
{
|
{
|
||||||
5 => (VideoRange.HDR, VideoRangeType.DOVI),
|
5 => (VideoRange.HDR, VideoRangeType.DOVI),
|
||||||
8 => dvBlCompatId switch
|
8 => dvBlCompatId switch
|
||||||
|
@ -796,32 +799,40 @@ namespace MediaBrowser.Model.Entities
|
||||||
1 => (VideoRange.HDR, VideoRangeType.DOVIWithHDR10),
|
1 => (VideoRange.HDR, VideoRangeType.DOVIWithHDR10),
|
||||||
4 => (VideoRange.HDR, VideoRangeType.DOVIWithHLG),
|
4 => (VideoRange.HDR, VideoRangeType.DOVIWithHLG),
|
||||||
2 => (VideoRange.SDR, VideoRangeType.DOVIWithSDR),
|
2 => (VideoRange.SDR, VideoRangeType.DOVIWithSDR),
|
||||||
// While not in Dolby Spec, Profile 8 CCid 6 media are possible to create, and since CCid 6 stems from Bluray (Profile 7 originally) an HDR10 base layer is guaranteed to exist.
|
// Out of Dolby Spec files should be marked as invalid
|
||||||
6 => (VideoRange.HDR, VideoRangeType.DOVIWithHDR10),
|
_ => (VideoRange.HDR, VideoRangeType.DOVIInvalid)
|
||||||
// There is no other case to handle here as per Dolby Spec. Default case included for completeness and linting purposes
|
|
||||||
_ => (VideoRange.SDR, VideoRangeType.SDR)
|
|
||||||
},
|
},
|
||||||
7 => (VideoRange.HDR, VideoRangeType.HDR10),
|
7 => (VideoRange.HDR, VideoRangeType.DOVIWithEL),
|
||||||
10 => dvBlCompatId switch
|
10 => dvBlCompatId switch
|
||||||
{
|
{
|
||||||
0 => (VideoRange.HDR, VideoRangeType.DOVI),
|
0 => (VideoRange.HDR, VideoRangeType.DOVI),
|
||||||
1 => (VideoRange.HDR, VideoRangeType.DOVIWithHDR10),
|
1 => (VideoRange.HDR, VideoRangeType.DOVIWithHDR10),
|
||||||
2 => (VideoRange.SDR, VideoRangeType.DOVIWithSDR),
|
2 => (VideoRange.SDR, VideoRangeType.DOVIWithSDR),
|
||||||
4 => (VideoRange.HDR, VideoRangeType.DOVIWithHLG),
|
4 => (VideoRange.HDR, VideoRangeType.DOVIWithHLG),
|
||||||
// While not in Dolby Spec, Profile 8 CCid 6 media are possible to create, and since CCid 6 stems from Bluray (Profile 7 originally) an HDR10 base layer is guaranteed to exist.
|
// Out of Dolby Spec files should be marked as invalid
|
||||||
6 => (VideoRange.HDR, VideoRangeType.DOVIWithHDR10),
|
_ => (VideoRange.HDR, VideoRangeType.DOVIInvalid)
|
||||||
// There is no other case to handle here as per Dolby Spec. Default case included for completeness and linting purposes
|
|
||||||
_ => (VideoRange.SDR, VideoRangeType.SDR)
|
|
||||||
},
|
},
|
||||||
_ => (VideoRange.SDR, VideoRangeType.SDR)
|
_ => (VideoRange.SDR, VideoRangeType.SDR)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (Hdr10PlusPresentFlag == true)
|
||||||
|
{
|
||||||
|
return dvRangeSet.Item2 switch
|
||||||
|
{
|
||||||
|
VideoRangeType.DOVIWithHDR10 => (VideoRange.HDR, VideoRangeType.DOVIWithHDR10Plus),
|
||||||
|
VideoRangeType.DOVIWithEL => (VideoRange.HDR, VideoRangeType.DOVIWithELHDR10Plus),
|
||||||
|
_ => dvRangeSet
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return dvRangeSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
var colorTransfer = ColorTransfer;
|
var colorTransfer = ColorTransfer;
|
||||||
|
|
||||||
if (string.Equals(colorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(colorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return (VideoRange.HDR, VideoRangeType.HDR10);
|
return Hdr10PlusPresentFlag == true ? (VideoRange.HDR, VideoRangeType.HDR10Plus) : (VideoRange.HDR, VideoRangeType.HDR10);
|
||||||
}
|
}
|
||||||
else if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
|
else if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
|
|
@ -99,4 +99,6 @@ public class MediaStreamInfo
|
||||||
public int? Rotation { get; set; }
|
public int? Rotation { get; set; }
|
||||||
|
|
||||||
public string? KeyFrames { get; set; }
|
public string? KeyFrames { get; set; }
|
||||||
|
|
||||||
|
public bool? Hdr10PlusPresentFlag { get; set; }
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,28 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Implementations.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddHdr10PlusFlag : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "Hdr10PlusPresentFlag",
|
||||||
|
table: "MediaStreamInfos",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Hdr10PlusPresentFlag",
|
||||||
|
table: "MediaStreamInfos");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -845,6 +845,9 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||||
b.Property<int?>("ElPresentFlag")
|
b.Property<int?>("ElPresentFlag")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool?>("Hdr10PlusPresentFlag")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<int?>("Height")
|
b.Property<int?>("Height")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue