Dmitry Lyzo 2 weeks ago committed by GitHub
commit e01570e9a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -22,6 +22,7 @@ namespace MediaBrowser.Model.Dlna
internal const TranscodeReason AudioReasons = TranscodeReason.AudioCodecNotSupported | TranscodeReason.AudioBitrateNotSupported | TranscodeReason.AudioChannelsNotSupported | TranscodeReason.AudioProfileNotSupported | TranscodeReason.AudioSampleRateNotSupported | TranscodeReason.SecondaryAudioNotSupported | TranscodeReason.AudioBitDepthNotSupported | TranscodeReason.AudioIsExternal;
internal const TranscodeReason VideoReasons = TranscodeReason.VideoCodecNotSupported | TranscodeReason.VideoResolutionNotSupported | TranscodeReason.AnamorphicVideoNotSupported | TranscodeReason.InterlacedVideoNotSupported | TranscodeReason.VideoBitDepthNotSupported | TranscodeReason.VideoBitrateNotSupported | TranscodeReason.VideoFramerateNotSupported | TranscodeReason.VideoLevelNotSupported | TranscodeReason.RefFramesNotSupported;
internal const TranscodeReason DirectStreamReasons = AudioReasons | TranscodeReason.ContainerNotSupported;
internal const TranscodeReason GenericReasons = TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported | TranscodeReason.ContainerNotSupported;
private readonly ILogger _logger;
private readonly ITranscoderSupport _transcoderSupport;
@ -72,6 +73,8 @@ namespace MediaBrowser.Model.Dlna
private StreamInfo? GetOptimalAudioStream(MediaSourceInfo item, MediaOptions options)
{
CheckCompatibility(MediaType.Audio, item, options.Profile);
var playlistItem = new StreamInfo
{
ItemId = options.ItemId,
@ -203,6 +206,213 @@ namespace MediaBrowser.Model.Dlna
return GetOptimalStream(streams, options.GetMaxBitrate(false) ?? 0);
}
/// <summary>
/// Sets compatibility errors (if any) for streams.
/// </summary>
/// <param name="mediaType">The <see cref="MediaType"/> of object for which to check compatibility.</param>
/// <param name="options">The <see cref="MediaOptions"/> object for which to check compatibility.</param>
public void CheckCompatibility(MediaType mediaType, MediaOptions options)
{
foreach (var mediaSource in options.MediaSources)
{
CheckCompatibility(mediaType, mediaSource, options.Profile);
}
}
/// <summary>
/// Sets compatibility errors (if any) for streams.
/// </summary>
/// <param name="mediaType">The <see cref="MediaType"/> of object for which to check compatibility.</param>
/// <param name="mediaSource">The <see cref="MediaSourceInfo"/>.</param>
/// <param name="profile">The <see cref="DeviceProfile"/>.</param>
public void CheckCompatibility(MediaType mediaType, MediaSourceInfo mediaSource, DeviceProfile profile)
{
foreach (var stream in mediaSource.MediaStreams)
{
if (!stream.DirectPlayErrors.HasValue)
{
stream.DirectPlayErrors = GetCompatibility(mediaType, mediaSource, stream, profile);
}
}
}
/// <summary>
/// Gets stream compatibility errors, if any.
/// </summary>
/// <param name="mediaType">The <see cref="MediaType"/> of object for which to check compatibility.</param>
/// <param name="mediaSource">The <see cref="MediaSourceInfo"/>.</param>
/// <param name="stream">The <see cref="MediaStream"/> for which to check compatibility.</param>
/// <param name="profile">The <see cref="DeviceProfile"/>.</param>
/// <returns>Stream compatibility errors, if any.</returns>
private TranscodeReason GetCompatibility(MediaType mediaType, MediaSourceInfo mediaSource, MediaStream stream, DeviceProfile profile)
{
switch (stream.Type)
{
case MediaStreamType.Audio:
return GetCompatibilityAudio(mediaSource, stream, profile, mediaType == MediaType.Video);
case MediaStreamType.Video:
return GetCompatibilityVideo(mediaSource, stream, profile);
case MediaStreamType.Subtitle:
var subtitleProfile = GetSubtitleProfile(mediaSource, stream, profile.SubtitleProfiles, PlayMethod.DirectPlay, _transcoderSupport, mediaSource.Container, null);
if (subtitleProfile.Method != SubtitleDeliveryMethod.Drop
&& subtitleProfile.Method != SubtitleDeliveryMethod.External
&& subtitleProfile.Method != SubtitleDeliveryMethod.Embed)
{
return TranscodeReason.SubtitleCodecNotSupported;
}
break;
}
return 0;
}
/// <summary>
/// Gets video stream compatibility errors, if any.
/// </summary>
/// <param name="mediaSource">The <see cref="MediaSourceInfo"/>.</param>
/// <param name="videoStream">The <see cref="MediaStream"/> of the video stream.</param>
/// <param name="profile">The <see cref="DeviceProfile"/>.</param>
/// <returns>Video stream compatibility errors, if any.</returns>
private TranscodeReason GetCompatibilityVideo(MediaSourceInfo mediaSource, MediaStream videoStream, DeviceProfile profile)
{
string container = mediaSource.Container;
string videoCodec = videoStream.Codec;
bool containerSupported = false;
bool videoSupported = false;
foreach (var directPlayProfile in profile.DirectPlayProfiles)
{
if (directPlayProfile.Type == DlnaProfileType.Video && directPlayProfile.SupportsContainer(container))
{
containerSupported = true;
if (directPlayProfile.SupportsVideoCodec(videoCodec))
{
videoSupported = true;
}
if (containerSupported && videoSupported)
{
break;
}
}
}
TranscodeReason failures = default;
if (!containerSupported)
{
failures |= TranscodeReason.ContainerNotSupported;
}
if (!videoSupported)
{
return failures | TranscodeReason.VideoCodecNotSupported;
}
// Video
int? width = videoStream.Width;
int? height = videoStream.Height;
int? bitDepth = videoStream.BitDepth;
int? videoBitrate = videoStream.BitRate;
double? videoLevel = videoStream.Level;
string? videoProfile = videoStream.Profile;
VideoRangeType? videoRangeType = videoStream.VideoRangeType;
float videoFramerate = videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
bool? isAnamorphic = videoStream.IsAnamorphic;
bool? isInterlaced = videoStream.IsInterlaced;
string? videoCodecTag = videoStream.CodecTag;
bool? isAvc = videoStream.IsAVC;
TransportStreamTimestamp? timestamp = mediaSource.Timestamp;
int? packetLength = videoStream.PacketLength;
int? refFrames = videoStream.RefFrames;
int? numAudioStreams = mediaSource.GetStreamCount(MediaStreamType.Audio);
int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
var checkVideoConditions = (ProfileCondition[] conditions) =>
conditions.Where(applyCondition => !ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc));
failures |= AggregateFailureConditions(
mediaSource,
profile,
"VideoCodecProfile",
profile.CodecProfiles
.Where(codecProfile => codecProfile.Type == CodecType.Video &&
codecProfile.ContainsAnyCodec(videoCodec, container) &&
!checkVideoConditions(codecProfile.ApplyConditions).Any())
.SelectMany(codecProfile => checkVideoConditions(codecProfile.Conditions)));
return failures;
}
/// <summary>
/// Gets audio stream compatibility errors, if any.
/// </summary>
/// <param name="mediaSource">The <see cref="MediaSourceInfo"/>.</param>
/// <param name="audioStream">The <see cref="MediaStream"/> of the audio stream.</param>
/// <param name="profile">The <see cref="DeviceProfile"/>.</param>
/// <param name="isVideo">True if source is video.</param>
/// <returns>Audio stream compatibility errors, if any.</returns>
private TranscodeReason GetCompatibilityAudio(MediaSourceInfo mediaSource, MediaStream audioStream, DeviceProfile profile, bool isVideo)
{
string container = mediaSource.Container;
string audioCodec = audioStream.Codec;
DlnaProfileType profileType = isVideo ? DlnaProfileType.Video : DlnaProfileType.Audio;
bool containerSupported = false;
bool audioSupported = false;
foreach (var directPlayProfile in profile.DirectPlayProfiles)
{
if (directPlayProfile.Type == profileType && directPlayProfile.SupportsContainer(container))
{
containerSupported = true;
if (directPlayProfile.SupportsAudioCodec(audioCodec))
{
audioSupported = true;
}
if (containerSupported && audioSupported)
{
break;
}
}
}
TranscodeReason failures = default;
if (!containerSupported)
{
failures |= TranscodeReason.ContainerNotSupported;
}
if (!audioSupported)
{
return failures | TranscodeReason.AudioCodecNotSupported;
}
var audioFailureConditions = isVideo
? GetProfileConditionsForVideoAudio(profile.CodecProfiles, container, audioCodec, audioStream.Channels, audioStream.BitRate, audioStream.SampleRate, audioStream.BitDepth, audioStream.Profile, mediaSource.IsSecondaryAudio(audioStream))
: GetProfileConditionsForAudio(profile.CodecProfiles, container, audioCodec, audioStream.Channels, audioStream.BitRate, audioStream.SampleRate, audioStream.BitDepth, true);
failures |= AggregateFailureConditions(mediaSource, profile, "AudioCodecProfile", audioFailureConditions);
if (audioStream.IsExternal)
{
failures |= TranscodeReason.AudioIsExternal;
}
return failures;
}
private static StreamInfo? GetOptimalStream(List<StreamInfo> streams, long maxBitrate)
=> SortMediaSources(streams, maxBitrate).FirstOrDefault();
@ -573,6 +783,8 @@ namespace MediaBrowser.Model.Dlna
{
ArgumentNullException.ThrowIfNull(item);
CheckCompatibility(MediaType.Video, item, options.Profile);
StreamInfo playlistItem = new StreamInfo
{
ItemId = options.ItemId,
@ -1162,32 +1374,18 @@ namespace MediaBrowser.Model.Dlna
.Where(containerProfile => containerProfile.Type == DlnaProfileType.Video && containerProfile.ContainsContainer(container))
.SelectMany(containerProfile => checkVideoConditions(containerProfile.Conditions)));
// Check video conditions
var videoCodecProfileReasons = AggregateFailureConditions(
mediaSource,
profile,
"VideoCodecProfile",
profile.CodecProfiles
.Where(codecProfile => codecProfile.Type == CodecType.Video &&
codecProfile.ContainsAnyCodec(videoStream?.Codec, container) &&
!checkVideoConditions(codecProfile.ApplyConditions).Any())
.SelectMany(codecProfile => checkVideoConditions(codecProfile.Conditions)));
// FIXME: Throw if CompatibilityErrors has no value?
var videoCodecProfileReasons = (videoStream?.DirectPlayErrors ?? 0) & ~GenericReasons;
// Check audiocandidates profile conditions
var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream));
// FIXME: Throw if CompatibilityErrors has no value?
var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => (audioStream.DirectPlayErrors ?? 0) & ~GenericReasons);
TranscodeReason subtitleProfileReasons = 0;
if (subtitleStream is not null)
{
var subtitleProfile = GetSubtitleProfile(mediaSource, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.DirectPlay, _transcoderSupport, container, null);
// FIXME: Throw if CompatibilityErrors has no value?
var subtitleProfileReasons = (subtitleStream?.DirectPlayErrors ?? 0) & ~GenericReasons;
if (subtitleProfile.Method != SubtitleDeliveryMethod.Drop
&& subtitleProfile.Method != SubtitleDeliveryMethod.External
&& subtitleProfile.Method != SubtitleDeliveryMethod.Embed)
{
_logger.LogDebug("Not eligible for {0} due to unsupported subtitles", PlayMethod.DirectPlay);
subtitleProfileReasons |= TranscodeReason.SubtitleCodecNotSupported;
}
if ((subtitleProfileReasons & TranscodeReason.SubtitleCodecNotSupported) != 0)
{
_logger.LogDebug("Not eligible for {0} due to unsupported subtitles", PlayMethod.DirectPlay);
}
var rankings = new[] { VideoReasons, AudioReasons, ContainerReasons };
@ -1303,20 +1501,6 @@ namespace MediaBrowser.Model.Dlna
return (Profile: null, PlayMethod: null, AudioStreamIndex: null, TranscodeReasons: failureReasons);
}
private TranscodeReason CheckVideoAudioStreamDirectPlay(MediaOptions options, MediaSourceInfo mediaSource, string container, MediaStream audioStream)
{
var profile = options.Profile;
var audioFailureConditions = GetProfileConditionsForVideoAudio(profile.CodecProfiles, container, audioStream.Codec, audioStream.Channels, audioStream.BitRate, audioStream.SampleRate, audioStream.BitDepth, audioStream.Profile, mediaSource.IsSecondaryAudio(audioStream));
var audioStreamFailureReasons = AggregateFailureConditions(mediaSource, profile, "VideoAudioCodecProfile", audioFailureConditions);
if (audioStream.IsExternal == true)
{
audioStreamFailureReasons |= TranscodeReason.AudioIsExternal;
}
return audioStreamFailureReasons;
}
private TranscodeReason AggregateFailureConditions(MediaSourceInfo mediaSource, DeviceProfile profile, string type, IEnumerable<ProfileCondition> conditions)
{
return conditions.Aggregate<ProfileCondition, TranscodeReason>(0, (reasons, i) =>

@ -12,6 +12,7 @@ using Jellyfin.Extensions;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Session;
namespace MediaBrowser.Model.Entities
{
@ -615,6 +616,24 @@ namespace MediaBrowser.Model.Entities
/// <value><c>true</c> if this instance is anamorphic; otherwise, <c>false</c>.</value>
public bool? IsAnamorphic { get; set; }
/// <summary>
/// Gets or sets DirectPlay errors.
/// </summary>
/// <value>DirectPlay errors.</value>
public TranscodeReason? DirectPlayErrors { get; set; }
/// <summary>
/// Gets a value indicating whether this instance is compatible with the device.
/// </summary>
/// <value><c>true</c> if this instance is compatible with the device; otherwise, <c>false</c>.</value>
public bool SupportsDirectPlay
{
get
{
return DirectPlayErrors == 0;
}
}
internal string GetResolutionText()
{
if (!Width.HasValue || !Height.HasValue)

@ -324,6 +324,39 @@ namespace Jellyfin.Model.Tests
Assert.Equal(streamInfo?.SubtitleStreamIndex, options.SubtitleStreamIndex);
}
[Theory]
// Chrome
[InlineData("Chrome", "mp4-h264-ac3-aac-aac-srt-2600k", new TranscodeReason[] { 0, TranscodeReason.AudioCodecNotSupported, TranscodeReason.SecondaryAudioNotSupported, TranscodeReason.SecondaryAudioNotSupported, 0 })]
[InlineData("Chrome", "mp4-h264-dts-srt-2600k", new TranscodeReason[] { 0, TranscodeReason.AudioCodecNotSupported, 0 })]
[InlineData("Chrome", "mkv-vp9-aac-srt-2600k", new TranscodeReason[] { TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported, TranscodeReason.ContainerNotSupported | TranscodeReason.AudioCodecNotSupported, 0 })]
// Firefox
[InlineData("Firefox", "mp4-h264-ac3-aac-aac-srt-2600k", new TranscodeReason[] { 0, TranscodeReason.AudioCodecNotSupported, TranscodeReason.SecondaryAudioNotSupported, TranscodeReason.SecondaryAudioNotSupported, 0 })]
[InlineData("Firefox", "mp4-h264-dts-srt-2600k", new TranscodeReason[] { 0, TranscodeReason.AudioCodecNotSupported, 0 })]
[InlineData("Firefox", "mkv-vp9-aac-srt-2600k", new TranscodeReason[] { TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported, TranscodeReason.ContainerNotSupported | TranscodeReason.AudioCodecNotSupported, 0 })]
// Tizen3-stereo
[InlineData("Tizen3-stereo", "mp4-h264-ac3-aac-aac-srt-2600k", new TranscodeReason[] { 0, 0, TranscodeReason.SecondaryAudioNotSupported, TranscodeReason.SecondaryAudioNotSupported, 0 })]
[InlineData("Tizen3-stereo", "mp4-h264-dts-srt-2600k", new TranscodeReason[] { 0, 0, 0 })]
[InlineData("Tizen3-stereo", "mkv-vp9-aac-srt-2600k", new TranscodeReason[] { 0, 0, 0 })]
// Tizen4-4K-5.1
[InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-aac-aac-srt-2600k", new TranscodeReason[] { 0, 0, TranscodeReason.SecondaryAudioNotSupported, TranscodeReason.SecondaryAudioNotSupported, 0 })]
[InlineData("Tizen4-4K-5.1", "mp4-h264-dts-srt-2600k", new TranscodeReason[] { 0, TranscodeReason.AudioCodecNotSupported, 0 })]
[InlineData("Tizen4-4K-5.1", "mkv-vp9-aac-srt-2600k", new TranscodeReason[] { 0, 0, 0 })]
public async Task CheckCompatibility(string deviceName, string mediaSourceName, TranscodeReason[] directPlayErrors)
{
var options = await GetMediaOptions(deviceName, mediaSourceName);
var builder = GetStreamBuilder();
var streamInfo = builder.GetOptimalVideoStream(options);
Assert.NotNull(streamInfo);
var mediaSource = options.MediaSources.First(source => source.Id == streamInfo.MediaSourceId);
Assert.NotNull(mediaSource);
Assert.Equal(directPlayErrors.Length, mediaSource.MediaStreams.Count);
Assert.All(mediaSource.MediaStreams, (stream, i) => Assert.Equal(stream.DirectPlayErrors, directPlayErrors[i]));
}
private StreamInfo? BuildVideoItemSimpleTest(MediaOptions options, PlayMethod? playMethod, TranscodeReason why, string transcodeMode, string transcodeProtocol)
{
if (string.IsNullOrEmpty(transcodeProtocol))

Loading…
Cancel
Save