aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Controller')
-rw-r--r--MediaBrowser.Controller/MediaEncoding/BitStreamFilterOptionType.cs32
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs211
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs1
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs7
4 files changed, 231 insertions, 20 deletions
diff --git a/MediaBrowser.Controller/MediaEncoding/BitStreamFilterOptionType.cs b/MediaBrowser.Controller/MediaEncoding/BitStreamFilterOptionType.cs
new file mode 100644
index 000000000..41d21e440
--- /dev/null
+++ b/MediaBrowser.Controller/MediaEncoding/BitStreamFilterOptionType.cs
@@ -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,
+}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index ed975af7f..afa962a41 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -162,6 +162,13 @@ namespace MediaBrowser.Controller.MediaEncoding
_configurationManager = configurationManager;
}
+ private enum DynamicHdrMetadataRemovalPlan
+ {
+ None,
+ RemoveDovi,
+ RemoveHdr10Plus,
+ }
+
[GeneratedRegex(@"\s+")]
private static partial Regex WhiteSpaceRegex();
@@ -342,11 +349,8 @@ namespace MediaBrowser.Controller.MediaEncoding
return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder || isVideoToolBoxDecoder;
}
- return state.VideoStream.VideoRange == VideoRange.HDR
- && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
- || state.VideoStream.VideoRangeType == VideoRangeType.HLG
- || state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10
- || state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithHLG);
+ // GPU tonemapping supports all HDR RangeTypes
+ return state.VideoStream.VideoRange == VideoRange.HDR;
}
private bool IsVulkanHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
@@ -381,8 +385,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
return state.VideoStream.VideoRange == VideoRange.HDR
- && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
- || state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10);
+ && IsDoviWithHdr10Bl(state.VideoStream);
}
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.
// All other HDR formats working.
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)
@@ -1301,6 +1305,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|| 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)
{
var codec = stream.Codec ?? string.Empty;
@@ -1308,8 +1319,125 @@ namespace MediaBrowser.Controller.MediaEncoding
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.
// https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb
if (IsH264(stream))
@@ -1317,21 +1445,57 @@ namespace MediaBrowser.Controller.MediaEncoding
return "-bsf:v h264_mp4toannexb";
}
+ if (IsAAC(stream))
+ {
+ // Convert adts header(mpegts) to asc header(mp4).
+ return "-bsf:a aac_adtstoasc";
+ }
+
if (IsH265(stream))
{
- return "-bsf:v hevc_mp4toannexb";
+ 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 (IsAAC(stream))
+ if (IsAv1(stream))
{
- // Convert adts header(mpegts) to asc header(mp4).
- return "-bsf:a aac_adtstoasc";
+ 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;
}
- public static string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceContainer)
+ public string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceContainer)
{
var bitStreamArgs = string.Empty;
var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
@@ -1342,7 +1506,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|| string.Equals(mediaSourceContainer, "aac", StringComparison.OrdinalIgnoreCase)
|| string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase)))
{
- bitStreamArgs = GetBitStreamArgs(state.AudioStream);
+ bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Audio);
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
-
var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.OrdinalIgnoreCase);
var requestHasHLG = requestedRangeTypes.Contains(VideoRangeType.HLG.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)
&& !((requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10)
|| (requestHasHLG && videoStream.VideoRangeType == VideoRangeType.DOVIWithHLG)
- || (requestHasSDR && videoStream.VideoRangeType == VideoRangeType.DOVIWithSDR)))
- {
- return false;
+ || (requestHasSDR && videoStream.VideoRangeType == VideoRangeType.DOVIWithSDR)
+ || (requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.HDR10Plus)))
+ {
+ // 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.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
{
- string bitStreamArgs = GetBitStreamArgs(state.VideoStream);
+ string bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Video);
if (!string.IsNullOrEmpty(bitStreamArgs))
{
args += " " + bitStreamArgs;
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index 7586ac902..8d6211051 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Globalization;
using System.Linq;
using Jellyfin.Data.Enums;
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index a60f52340..de6353c4c 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -117,6 +117,13 @@ namespace MediaBrowser.Controller.MediaEncoding
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>
/// Extracts the audio image.
/// </summary>
/// <param name="path">The path.</param>