diff options
Diffstat (limited to 'MediaBrowser.Model')
37 files changed, 631 insertions, 413 deletions
diff --git a/MediaBrowser.Model/Activity/IActivityManager.cs b/MediaBrowser.Model/Activity/IActivityManager.cs index 28073fb8d..95aa567ad 100644 --- a/MediaBrowser.Model/Activity/IActivityManager.cs +++ b/MediaBrowser.Model/Activity/IActivityManager.cs @@ -2,9 +2,9 @@ using System; using System.Threading.Tasks; -using Jellyfin.Data.Entities; using Jellyfin.Data.Events; using Jellyfin.Data.Queries; +using Jellyfin.Database.Implementations.Entities; using MediaBrowser.Model.Querying; namespace MediaBrowser.Model.Activity diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index bc4e6ef73..a58c01c96 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -83,9 +83,9 @@ public class ServerConfiguration : BaseApplicationConfiguration public bool QuickConnectAvailable { get; set; } = true; /// <summary> - /// Gets or sets a value indicating whether [enable case sensitive item ids]. + /// Gets or sets a value indicating whether [enable case-sensitive item ids]. /// </summary> - /// <value><c>true</c> if [enable case sensitive item ids]; otherwise, <c>false</c>.</value> + /// <value><c>true</c> if [enable case-sensitive item ids]; otherwise, <c>false</c>.</value> public bool EnableCaseSensitiveItemIds { get; set; } = true; public bool DisableLiveTvChannelUserDataName { get; set; } = true; @@ -178,6 +178,11 @@ public class ServerConfiguration : BaseApplicationConfiguration public int LibraryUpdateDuration { get; set; } = 30; /// <summary> + /// Gets or sets the maximum amount of items to cache. + /// </summary> + public int CacheSize { get; set; } = Environment.ProcessorCount * 100; + + /// <summary> /// Gets or sets the image saving convention. /// </summary> /// <value>The image saving convention.</value> @@ -199,7 +204,9 @@ public class ServerConfiguration : BaseApplicationConfiguration public bool EnableFolderView { get; set; } = false; - public bool EnableGroupingIntoCollections { get; set; } = false; + public bool EnableGroupingMoviesIntoCollections { get; set; } = false; + + public bool EnableGroupingShowsIntoCollections { get; set; } = false; public bool DisplaySpecialsWithinSeasons { get; set; } = true; @@ -249,7 +256,7 @@ public class ServerConfiguration : BaseApplicationConfiguration public bool AllowClientLogUpload { get; set; } = true; /// <summary> - /// Gets or sets the dummy chapter duration in seconds, use 0 (zero) or less to disable generation alltogether. + /// Gets or sets the dummy chapter duration in seconds, use 0 (zero) or less to disable generation altogether. /// </summary> /// <value>The dummy chapters duration.</value> public int DummyChapterDuration { get; set; } @@ -276,4 +283,9 @@ public class ServerConfiguration : BaseApplicationConfiguration /// </summary> /// <value>The trickplay options.</value> public TrickplayOptions TrickplayOptions { get; set; } = new TrickplayOptions(); + + /// <summary> + /// Gets or sets a value indicating whether old authorization methods are allowed. + /// </summary> + public bool EnableLegacyAuthorization { get; set; } = true; } diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs index b477f2593..fe4b2de65 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -1,7 +1,7 @@ #pragma warning disable CS1591 using System; -using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; namespace MediaBrowser.Model.Configuration { diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs index af0787990..09b966367 100644 --- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs +++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs @@ -25,9 +25,10 @@ namespace MediaBrowser.Model.Dlna /// <param name="videoFramerate">The framerate.</param> /// <param name="packetLength">The packet length.</param> /// <param name="timestamp">The <see cref="TransportStreamTimestamp"/>.</param> - /// <param name="isAnamorphic">A value indicating whether tthe video is anamorphic.</param> - /// <param name="isInterlaced">A value indicating whether tthe video is interlaced.</param> + /// <param name="isAnamorphic">A value indicating whether the video is anamorphic.</param> + /// <param name="isInterlaced">A value indicating whether the video is interlaced.</param> /// <param name="refFrames">The reference frames.</param> + /// <param name="numStreams">The number of streams.</param> /// <param name="numVideoStreams">The number of video streams.</param> /// <param name="numAudioStreams">The number of audio streams.</param> /// <param name="videoCodecTag">The video codec tag.</param> @@ -48,6 +49,7 @@ namespace MediaBrowser.Model.Dlna bool? isAnamorphic, bool? isInterlaced, int? refFrames, + int numStreams, int? numVideoStreams, int? numAudioStreams, string? videoCodecTag, @@ -83,6 +85,8 @@ namespace MediaBrowser.Model.Dlna return IsConditionSatisfied(condition, width); case ProfileConditionValue.RefFrames: return IsConditionSatisfied(condition, refFrames); + case ProfileConditionValue.NumStreams: + return IsConditionSatisfied(condition, numStreams); case ProfileConditionValue.NumAudioStreams: return IsConditionSatisfied(condition, numAudioStreams); case ProfileConditionValue.NumVideoStreams: diff --git a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs index 438df3441..553ccfc64 100644 --- a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs +++ b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs @@ -59,7 +59,7 @@ public class DirectPlayProfile /// <returns>True if supported.</returns> public bool SupportsAudioCodec(string? codec) { - // Video profiles can have audio codec restrictions too, therefore incude Video as valid type. + // Video profiles can have audio codec restrictions too, therefore include Video as valid type. return (Type == DlnaProfileType.Audio || Type == DlnaProfileType.Video) && ContainerHelper.ContainsContainer(AudioCodec, codec); } } diff --git a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs index a32433e18..b66a15840 100644 --- a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs +++ b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs @@ -27,6 +27,7 @@ namespace MediaBrowser.Model.Dlna IsInterlaced = 21, AudioSampleRate = 22, AudioBitDepth = 23, - VideoRangeType = 24 + VideoRangeType = 24, + NumStreams = 25 } } diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 767e01202..806900e9a 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -30,7 +30,7 @@ namespace MediaBrowser.Model.Dlna private readonly ITranscoderSupport _transcoderSupport; private static readonly string[] _supportedHlsVideoCodecs = ["h264", "hevc", "vp9", "av1"]; private static readonly string[] _supportedHlsAudioCodecsTs = ["aac", "ac3", "eac3", "mp3"]; - private static readonly string[] _supportedHlsAudioCodecsMp4 = ["aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd"]; + private static readonly string[] _supportedHlsAudioCodecsMp4 = ["aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dts", "truehd"]; /// <summary> /// Initializes a new instance of the <see cref="StreamBuilder"/> class. @@ -101,21 +101,16 @@ namespace MediaBrowser.Model.Dlna MediaStream audioStream = item.GetDefaultAudioStream(null); + ArgumentNullException.ThrowIfNull(audioStream); + var directPlayInfo = GetAudioDirectPlayProfile(item, audioStream, options); var directPlayMethod = directPlayInfo.PlayMethod; var transcodeReasons = directPlayInfo.TranscodeReasons; - var inputAudioChannels = audioStream?.Channels; - var inputAudioBitrate = audioStream?.BitRate; - var inputAudioSampleRate = audioStream?.SampleRate; - var inputAudioBitDepth = audioStream?.BitDepth; - if (directPlayMethod is PlayMethod.DirectPlay) { - var profile = options.Profile; - var audioFailureConditions = GetProfileConditionsForAudio(profile.CodecProfiles, item.Container, audioStream?.Codec, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, true); - var audioFailureReasons = AggregateFailureConditions(item, profile, "AudioCodecProfile", audioFailureConditions); + var audioFailureReasons = GetCompatibilityAudioCodec(options, item, item.Container, audioStream, null, false, false); transcodeReasons |= audioFailureReasons; if (audioFailureReasons == 0) @@ -188,6 +183,11 @@ namespace MediaBrowser.Model.Dlna SetStreamInfoOptionsFromTranscodingProfile(item, playlistItem, transcodingProfile); + var inputAudioChannels = audioStream.Channels; + var inputAudioBitrate = audioStream.BitRate; + var inputAudioSampleRate = audioStream.SampleRate; + var inputAudioBitDepth = audioStream.BitDepth; + var audioTranscodingConditions = GetProfileConditionsForAudio(options.Profile.CodecProfiles, transcodingProfile.Container, transcodingProfile.AudioCodec, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, false).ToArray(); ApplyTranscodingConditions(playlistItem, audioTranscodingConditions, null, true, true); @@ -338,6 +338,9 @@ namespace MediaBrowser.Model.Dlna case ProfileConditionValue.IsSecondaryAudio: return TranscodeReason.SecondaryAudioNotSupported; + case ProfileConditionValue.NumStreams: + return TranscodeReason.StreamCountExceedsLimit; + case ProfileConditionValue.NumAudioStreams: // TODO return 0; @@ -797,7 +800,7 @@ namespace MediaBrowser.Model.Dlna options.SubtitleStreamIndex, playlistItem.PlayMethod, playlistItem.TranscodeReasons, - playlistItem.ToUrl("media:", "<token>")); + playlistItem.ToUrl("media:", "<token>", null)); item.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video, directPlayProfile); return playlistItem; @@ -810,6 +813,10 @@ namespace MediaBrowser.Model.Dlna MediaStream? audioStream, StreamInfo playlistItem) { + var mediaSource = playlistItem.MediaSource; + + ArgumentNullException.ThrowIfNull(mediaSource); + if (!(item.SupportsTranscoding || item.SupportsDirectStream)) { return (null, null); @@ -824,17 +831,7 @@ namespace MediaBrowser.Model.Dlna } var videoCodec = videoStream?.Codec; - float videoFramerate = videoStream?.ReferenceFrameRate ?? 0; - TransportStreamTimestamp? timestamp = videoStream is null ? TransportStreamTimestamp.None : item.Timestamp; - int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio); - int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video); - var audioCodec = audioStream?.Codec; - var audioProfile = audioStream?.Profile; - var audioChannels = audioStream?.Channels; - var audioBitrate = audioStream?.BitRate; - var audioSampleRate = audioStream?.SampleRate; - var audioBitDepth = audioStream?.BitDepth; var analyzedProfiles = transcodingProfiles .Select(transcodingProfile => @@ -843,37 +840,40 @@ namespace MediaBrowser.Model.Dlna var container = transcodingProfile.Container; - if (options.AllowVideoStreamCopy) + if (videoStream is not null + && options.AllowVideoStreamCopy + && ContainerHelper.ContainsContainer(transcodingProfile.VideoCodec, videoCodec)) { - if (ContainerHelper.ContainsContainer(transcodingProfile.VideoCodec, videoCodec)) - { - var appliedVideoConditions = options.Profile.CodecProfiles - .Where(i => i.Type == CodecType.Video && - i.ContainsAnyCodec(videoCodec, container) && - i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.VideoRangeType, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC))) - .Select(i => - i.Conditions.All(condition => ConditionProcessor.IsVideoConditionSatisfied(condition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.VideoRangeType, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC))); - - // An empty appliedVideoConditions means that the codec has no conditions for the current video stream - var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied); - rank.Video = conditionsSatisfied ? 1 : 2; - } + var failures = GetCompatibilityVideoCodec(options, mediaSource, container, videoStream); + rank.Video = failures == 0 ? 1 : 2; } - if (options.AllowAudioStreamCopy) + if (audioStream is not null + && options.AllowAudioStreamCopy) { - if (ContainerHelper.ContainsContainer(transcodingProfile.AudioCodec, audioCodec)) + // For Audio stream, we prefer the audio codec that can be directly copied, then the codec that can otherwise satisfies + // the transcoding conditions, then the one does not satisfy the transcoding conditions. + // For example: A client can support both aac and flac, but flac only supports 2 channels while aac supports 6. + // When the source audio is 6 channel flac, we should transcode to 6 channel aac, instead of down-mix to 2 channel flac. + var transcodingAudioCodecs = ContainerHelper.Split(transcodingProfile.AudioCodec); + + foreach (var transcodingAudioCodec in transcodingAudioCodecs) { - var appliedVideoConditions = options.Profile.CodecProfiles - .Where(i => i.Type == CodecType.VideoAudio && - i.ContainsAnyCodec(audioCodec, container) && - i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, false))) - .Select(i => - i.Conditions.All(condition => ConditionProcessor.IsVideoAudioConditionSatisfied(condition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, false))); - - // An empty appliedVideoConditions means that the codec has no conditions for the current audio stream - var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied); - rank.Audio = conditionsSatisfied ? 1 : 2; + var failures = GetCompatibilityAudioCodec(options, mediaSource, container, audioStream, transcodingAudioCodec, true, false); + + var rankAudio = 3; + + if (failures == 0) + { + rankAudio = string.Equals(transcodingAudioCodec, audioCodec, StringComparison.OrdinalIgnoreCase) ? 1 : 2; + } + + rank.Audio = Math.Min(rank.Audio, rankAudio); + + if (rank.Audio == 1) + { + break; + } } } @@ -963,9 +963,18 @@ namespace MediaBrowser.Model.Dlna var audioStreamWithSupportedCodec = candidateAudioStreams.Where(stream => ContainerHelper.ContainsContainer(audioCodecs, false, stream.Codec)).FirstOrDefault(); - var directAudioStream = audioStreamWithSupportedCodec?.Channels is not null && audioStreamWithSupportedCodec.Channels.Value <= (playlistItem.TranscodingMaxAudioChannels ?? int.MaxValue) ? audioStreamWithSupportedCodec : null; + var channelsExceedsLimit = audioStreamWithSupportedCodec is not null && audioStreamWithSupportedCodec.Channels > (playlistItem.TranscodingMaxAudioChannels ?? int.MaxValue); + + var directAudioFailures = audioStreamWithSupportedCodec is null ? default : GetCompatibilityAudioCodec(options, item, container ?? string.Empty, audioStreamWithSupportedCodec, null, true, false); + + playlistItem.TranscodeReasons |= directAudioFailures; + + var directAudioStreamSatisfied = audioStreamWithSupportedCodec is not null && !channelsExceedsLimit + && directAudioFailures == 0; + + directAudioStreamSatisfied = directAudioStreamSatisfied && !playlistItem.TranscodeReasons.HasFlag(TranscodeReason.ContainerBitrateExceedsLimit); - var channelsExceedsLimit = audioStreamWithSupportedCodec is not null && directAudioStream is null; + var directAudioStream = directAudioStreamSatisfied ? audioStreamWithSupportedCodec : null; if (channelsExceedsLimit && playlistItem.TargetAudioStream is not null) { @@ -1013,6 +1022,7 @@ namespace MediaBrowser.Model.Dlna int? packetLength = videoStream?.PacketLength; int? refFrames = videoStream?.RefFrames; + int numStreams = item.MediaStreams.Count; int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio); int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video); @@ -1021,7 +1031,7 @@ namespace MediaBrowser.Model.Dlna var appliedVideoConditions = options.Profile.CodecProfiles .Where(i => i.Type == CodecType.Video && i.ContainsAnyCodec(playlistItem.VideoCodecs, container, useSubContainer) && - i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))) + i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numStreams, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))) // Reverse codec profiles for backward compatibility - first codec profile has higher priority .Reverse(); foreach (var condition in appliedVideoConditions) @@ -1087,12 +1097,12 @@ namespace MediaBrowser.Model.Dlna _logger.LogDebug( "Transcode Result for Profile: {Profile}, Path: {Path}, PlayMethod: {PlayMethod}, AudioStreamIndex: {AudioStreamIndex}, SubtitleStreamIndex: {SubtitleStreamIndex}, Reasons: {TranscodeReason}", - options.Profile?.Name ?? "Anonymous Profile", + options.Profile.Name ?? "Anonymous Profile", item.Path ?? "Unknown path", - playlistItem?.PlayMethod, + playlistItem.PlayMethod, audioStream?.Index, - playlistItem?.SubtitleStreamIndex, - playlistItem?.TranscodeReasons); + playlistItem.SubtitleStreamIndex, + playlistItem.TranscodeReasons); } private static int GetDefaultAudioBitrate(string? audioCodec, int? audioChannels) @@ -1251,52 +1261,14 @@ namespace MediaBrowser.Model.Dlna DeviceProfile profile = options.Profile; string container = mediaSource.Container; - // 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 is null ? 0 : videoStream.ReferenceFrameRate ?? 0; - bool? isAnamorphic = videoStream?.IsAnamorphic; - bool? isInterlaced = videoStream?.IsInterlaced; - string? videoCodecTag = videoStream?.CodecTag; - bool? isAvc = videoStream?.IsAVC; - - TransportStreamTimestamp? timestamp = videoStream is null ? TransportStreamTimestamp.None : 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)); - // Check container conditions - var containerProfileReasons = AggregateFailureConditions( - mediaSource, - profile, - "VideoCodecProfile", - profile.ContainerProfiles - .Where(containerProfile => containerProfile.Type == DlnaProfileType.Video && containerProfile.ContainsContainer(container)) - .SelectMany(containerProfile => checkVideoConditions(containerProfile.Conditions))); + var containerProfileReasons = GetCompatibilityContainer(options, mediaSource, container, videoStream); // 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))); + var videoCodecProfileReasons = videoStream is null ? default : GetCompatibilityVideoCodec(options, mediaSource, container, videoStream); // Check audio candidates profile conditions - var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream)); + var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => GetCompatibilityAudioCodecDirect(options, mediaSource, container, audioStream, true, mediaSource.IsSecondaryAudio(audioStream) ?? false)); TranscodeReason subtitleProfileReasons = 0; if (subtitleStream is not null) @@ -1409,20 +1381,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) => @@ -1896,6 +1854,7 @@ namespace MediaBrowser.Model.Dlna case ProfileConditionValue.AudioProfile: case ProfileConditionValue.Has64BitOffsets: case ProfileConditionValue.PacketLength: + case ProfileConditionValue.NumStreams: case ProfileConditionValue.NumAudioStreams: case ProfileConditionValue.NumVideoStreams: case ProfileConditionValue.IsSecondaryAudio: @@ -2213,7 +2172,7 @@ namespace MediaBrowser.Model.Dlna } } - private static bool IsAudioDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream) + private static bool IsAudioContainerSupported(DirectPlayProfile profile, MediaSourceInfo item) { // Check container type if (!profile.SupportsContainer(item.Container)) @@ -2221,6 +2180,20 @@ namespace MediaBrowser.Model.Dlna return false; } + // Never direct play audio in matroska when the device only declare support for webm. + // The first check is not enough because mkv is assumed can be webm. + // See https://github.com/jellyfin/jellyfin/issues/13344 + return !ContainerHelper.ContainsContainer("mkv", item.Container) + || profile.SupportsContainer("mkv"); + } + + private static bool IsAudioDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream) + { + if (!IsAudioContainerSupported(profile, item)) + { + return false; + } + // Check audio codec string? audioCodec = audioStream?.Codec; if (!profile.SupportsAudioCodec(audioCodec)) @@ -2235,19 +2208,16 @@ namespace MediaBrowser.Model.Dlna { // Check container type, this should NOT be supported // If the container is supported, the file should be directly played - if (!profile.SupportsContainer(item.Container)) + if (IsAudioContainerSupported(profile, item)) { - // Check audio codec, we cannot use the SupportsAudioCodec here - // Because that one assumes empty container supports all codec, which is just useless - string? audioCodec = audioStream?.Codec; - if (string.Equals(profile.AudioCodec, audioCodec, StringComparison.OrdinalIgnoreCase) || - string.Equals(profile.Container, audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return true; - } + return false; } - return false; + // Check audio codec, we cannot use the SupportsAudioCodec here + // Because that one assumes empty container supports all codec, which is just useless + string? audioCodec = audioStream?.Codec; + return string.Equals(profile.AudioCodec, audioCodec, StringComparison.OrdinalIgnoreCase) + || string.Equals(profile.Container, audioCodec, StringComparison.OrdinalIgnoreCase); } private int GetRank(ref TranscodeReason a, TranscodeReason[] rankings) @@ -2266,5 +2236,141 @@ namespace MediaBrowser.Model.Dlna return index; } + + /// <summary> + /// Check the profile conditions. + /// </summary> + /// <param name="conditions">Profile conditions.</param> + /// <param name="mediaSource">Media source.</param> + /// <param name="videoStream">Video stream.</param> + /// <returns>Failed profile conditions.</returns> + private IEnumerable<ProfileCondition> CheckVideoConditions(ProfileCondition[] conditions, MediaSourceInfo mediaSource, MediaStream? videoStream) + { + 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 is null ? 0 : videoStream.ReferenceFrameRate ?? 0; + bool? isAnamorphic = videoStream?.IsAnamorphic; + bool? isInterlaced = videoStream?.IsInterlaced; + string? videoCodecTag = videoStream?.CodecTag; + bool? isAvc = videoStream?.IsAVC; + + TransportStreamTimestamp? timestamp = videoStream is null ? TransportStreamTimestamp.None : mediaSource.Timestamp; + int? packetLength = videoStream?.PacketLength; + int? refFrames = videoStream?.RefFrames; + + int numStreams = mediaSource.MediaStreams.Count; + int? numAudioStreams = mediaSource.GetStreamCount(MediaStreamType.Audio); + int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video); + + return conditions.Where(applyCondition => !ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numStreams, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)); + } + + /// <summary> + /// Check the compatibility of the container. + /// </summary> + /// <param name="options">Media options.</param> + /// <param name="mediaSource">Media source.</param> + /// <param name="container">Container.</param> + /// <param name="videoStream">Video stream.</param> + /// <returns>Transcode reasons if the container is not fully compatible.</returns> + private TranscodeReason GetCompatibilityContainer(MediaOptions options, MediaSourceInfo mediaSource, string container, MediaStream? videoStream) + { + var profile = options.Profile; + + var failures = AggregateFailureConditions( + mediaSource, + profile, + "VideoCodecProfile", + profile.ContainerProfiles + .Where(containerProfile => containerProfile.Type == DlnaProfileType.Video && containerProfile.ContainsContainer(container)) + .SelectMany(containerProfile => CheckVideoConditions(containerProfile.Conditions, mediaSource, videoStream))); + + return failures; + } + + /// <summary> + /// Check the compatibility of the video codec. + /// </summary> + /// <param name="options">Media options.</param> + /// <param name="mediaSource">Media source.</param> + /// <param name="container">Container.</param> + /// <param name="videoStream">Video stream.</param> + /// <returns>Transcode reasons if the video stream is not fully compatible.</returns> + private TranscodeReason GetCompatibilityVideoCodec(MediaOptions options, MediaSourceInfo mediaSource, string container, MediaStream videoStream) + { + var profile = options.Profile; + + string videoCodec = videoStream.Codec; + + var failures = AggregateFailureConditions( + mediaSource, + profile, + "VideoCodecProfile", + profile.CodecProfiles + .Where(codecProfile => codecProfile.Type == CodecType.Video && + codecProfile.ContainsAnyCodec(videoCodec, container) && + !CheckVideoConditions(codecProfile.ApplyConditions, mediaSource, videoStream).Any()) + .SelectMany(codecProfile => CheckVideoConditions(codecProfile.Conditions, mediaSource, videoStream))); + + return failures; + } + + /// <summary> + /// Check the compatibility of the audio codec. + /// </summary> + /// <param name="options">Media options.</param> + /// <param name="mediaSource">Media source.</param> + /// <param name="container">Container.</param> + /// <param name="audioStream">Audio stream.</param> + /// <param name="transcodingAudioCodec">Override audio codec.</param> + /// <param name="isVideo">The media source is video.</param> + /// <param name="isSecondaryAudio">The audio stream is secondary.</param> + /// <returns>Transcode reasons if the audio stream is not fully compatible.</returns> + private TranscodeReason GetCompatibilityAudioCodec(MediaOptions options, MediaSourceInfo mediaSource, string container, MediaStream audioStream, string? transcodingAudioCodec, bool isVideo, bool isSecondaryAudio) + { + var profile = options.Profile; + + var audioCodec = transcodingAudioCodec ?? audioStream.Codec; + var audioProfile = audioStream.Profile; + var audioChannels = audioStream.Channels; + var audioBitrate = audioStream.BitRate; + var audioSampleRate = audioStream.SampleRate; + var audioBitDepth = audioStream.BitDepth; + + var audioFailureConditions = isVideo + ? GetProfileConditionsForVideoAudio(profile.CodecProfiles, container, audioCodec, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio) + : GetProfileConditionsForAudio(profile.CodecProfiles, container, audioCodec, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, true); + + var failures = AggregateFailureConditions(mediaSource, profile, "AudioCodecProfile", audioFailureConditions); + + return failures; + } + + /// <summary> + /// Check the compatibility of the audio codec for direct playback. + /// </summary> + /// <param name="options">Media options.</param> + /// <param name="mediaSource">Media source.</param> + /// <param name="container">Container.</param> + /// <param name="audioStream">Audio stream.</param> + /// <param name="isVideo">The media source is video.</param> + /// <param name="isSecondaryAudio">The audio stream is secondary.</param> + /// <returns>Transcode reasons if the audio stream is not fully compatible for direct playback.</returns> + private TranscodeReason GetCompatibilityAudioCodecDirect(MediaOptions options, MediaSourceInfo mediaSource, string container, MediaStream audioStream, bool isVideo, bool isSecondaryAudio) + { + var failures = GetCompatibilityAudioCodec(options, mediaSource, container, audioStream, null, isVideo, isSecondaryAudio); + + if (audioStream.IsExternal) + { + failures |= TranscodeReason.AudioIsExternal; + } + + return failures; + } } } diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 1ae4e1962..f9aab2d67 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -1,7 +1,12 @@ +#pragma warning disable CA1819 // Properties should not return arrays + using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; +using System.Text; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -871,202 +876,279 @@ public class StreamInfo /// </summary> /// <param name="baseUrl">The base Url.</param> /// <param name="accessToken">The access Token.</param> + /// <param name="query">Optional extra query.</param> /// <returns>A querystring representation of this object.</returns> - public string ToUrl(string baseUrl, string? accessToken) + public string ToUrl(string? baseUrl, string? accessToken, string? query) { - ArgumentException.ThrowIfNullOrEmpty(baseUrl); + var sb = new StringBuilder(); + if (!string.IsNullOrEmpty(baseUrl)) + { + sb.Append(baseUrl.TrimEnd('/')); + } - List<string> list = []; - foreach (NameValuePair pair in BuildParams(this, accessToken)) + if (MediaType == DlnaProfileType.Audio) { - if (string.IsNullOrEmpty(pair.Value)) - { - continue; - } + sb.Append("/audio/"); + } + else + { + sb.Append("/videos/"); + } - // Try to keep the url clean by omitting defaults - if (string.Equals(pair.Name, "StartTimeTicks", StringComparison.OrdinalIgnoreCase) - && string.Equals(pair.Value, "0", StringComparison.OrdinalIgnoreCase)) - { - continue; - } + sb.Append(ItemId); - if (string.Equals(pair.Name, "SubtitleStreamIndex", StringComparison.OrdinalIgnoreCase) - && string.Equals(pair.Value, "-1", StringComparison.OrdinalIgnoreCase)) - { - continue; - } + if (SubProtocol == MediaStreamProtocol.hls) + { + sb.Append("/master.m3u8?"); + } + else + { + sb.Append("/stream"); - if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase) - && string.Equals(pair.Value, "false", StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(Container)) { - continue; + sb.Append('.'); + sb.Append(Container); } - var encodedValue = pair.Value.Replace(" ", "%20", StringComparison.Ordinal); + sb.Append('?'); + } + + if (!string.IsNullOrEmpty(DeviceProfileId)) + { + sb.Append("&DeviceProfileId="); + sb.Append(DeviceProfileId); + } - list.Add(string.Format(CultureInfo.InvariantCulture, "{0}={1}", pair.Name, encodedValue)); + if (!string.IsNullOrEmpty(DeviceId)) + { + sb.Append("&DeviceId="); + sb.Append(DeviceId); } - string queryString = string.Join('&', list); + if (!string.IsNullOrEmpty(MediaSourceId)) + { + sb.Append("&MediaSourceId="); + sb.Append(MediaSourceId); + } - return GetUrl(baseUrl, queryString); - } + // default true so don't store. + if (IsDirectStream) + { + sb.Append("&Static=true"); + } - private string GetUrl(string baseUrl, string queryString) - { - ArgumentException.ThrowIfNullOrEmpty(baseUrl); + if (VideoCodecs.Count != 0) + { + sb.Append("&VideoCodec="); + sb.AppendJoin(',', VideoCodecs); + } - string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container; + if (AudioCodecs.Count != 0) + { + sb.Append("&AudioCodec="); + sb.AppendJoin(',', AudioCodecs); + } - baseUrl = baseUrl.TrimEnd('/'); + if (AudioStreamIndex.HasValue) + { + sb.Append("&AudioStreamIndex="); + sb.Append(AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture)); + } - if (MediaType == DlnaProfileType.Audio) + if (SubtitleStreamIndex.HasValue && SubtitleDeliveryMethod != SubtitleDeliveryMethod.External && SubtitleStreamIndex != -1) { - if (SubProtocol == MediaStreamProtocol.hls) - { - return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString); - } + sb.Append("&SubtitleStreamIndex="); + sb.Append(SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture)); + sb.Append("&SubtitleMethod="); + sb.Append(SubtitleDeliveryMethod.ToString()); + } - return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString); + if (VideoBitrate.HasValue) + { + sb.Append("&VideoBitrate="); + sb.Append(VideoBitrate.Value.ToString(CultureInfo.InvariantCulture)); } - if (SubProtocol == MediaStreamProtocol.hls) + if (AudioBitrate.HasValue) { - return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString); + sb.Append("&AudioBitrate="); + sb.Append(AudioBitrate.Value.ToString(CultureInfo.InvariantCulture)); } - return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString); - } + if (AudioSampleRate.HasValue) + { + sb.Append("&AudioSampleRate="); + sb.Append(AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture)); + } - private static List<NameValuePair> BuildParams(StreamInfo item, string? accessToken) - { - List<NameValuePair> list = []; + if (MaxFramerate.HasValue) + { + sb.Append("&MaxFramerate="); + sb.Append(MaxFramerate.Value.ToString(CultureInfo.InvariantCulture)); + } - string audioCodecs = item.AudioCodecs.Count == 0 ? - string.Empty : - string.Join(',', item.AudioCodecs); + if (MaxWidth.HasValue) + { + sb.Append("&MaxWidth="); + sb.Append(MaxWidth.Value.ToString(CultureInfo.InvariantCulture)); + } - string videoCodecs = item.VideoCodecs.Count == 0 ? - string.Empty : - string.Join(',', item.VideoCodecs); + if (MaxHeight.HasValue) + { + sb.Append("&MaxHeight="); + sb.Append(MaxHeight.Value.ToString(CultureInfo.InvariantCulture)); + } - list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty)); - list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty)); - list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty)); - list.Add(new NameValuePair("Static", item.IsDirectStream.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); - list.Add(new NameValuePair("VideoCodec", videoCodecs)); - list.Add(new NameValuePair("AudioCodec", audioCodecs)); - list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? item.AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); - list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && (item.AlwaysBurnInSubtitleWhenTranscoding || item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External) ? item.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); - list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? item.VideoBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); - list.Add(new NameValuePair("AudioBitrate", item.AudioBitrate.HasValue ? item.AudioBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); - list.Add(new NameValuePair("AudioSampleRate", item.AudioSampleRate.HasValue ? item.AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + if (SubProtocol == MediaStreamProtocol.hls) + { + if (!string.IsNullOrEmpty(Container)) + { + sb.Append("&SegmentContainer="); + sb.Append(Container); + } - list.Add(new NameValuePair("MaxFramerate", item.MaxFramerate.HasValue ? item.MaxFramerate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); - list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? item.MaxWidth.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); - list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? item.MaxHeight.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + if (SegmentLength.HasValue) + { + sb.Append("&SegmentLength="); + sb.Append(SegmentLength.Value.ToString(CultureInfo.InvariantCulture)); + } - long startPositionTicks = item.StartPositionTicks; + if (MinSegments.HasValue) + { + sb.Append("&MinSegments="); + sb.Append(MinSegments.Value.ToString(CultureInfo.InvariantCulture)); + } - if (item.SubProtocol == MediaStreamProtocol.hls) - { - list.Add(new NameValuePair("StartTimeTicks", string.Empty)); + sb.Append("&BreakOnNonKeyFrames="); + sb.Append(BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture)); } else { - list.Add(new NameValuePair("StartTimeTicks", startPositionTicks.ToString(CultureInfo.InvariantCulture))); + if (StartPositionTicks != 0) + { + sb.Append("&StartTimeTicks="); + sb.Append(StartPositionTicks.ToString(CultureInfo.InvariantCulture)); + } } - list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty)); - list.Add(new NameValuePair("api_key", accessToken ?? string.Empty)); + if (!string.IsNullOrEmpty(PlaySessionId)) + { + sb.Append("&PlaySessionId="); + sb.Append(PlaySessionId); + } - string? liveStreamId = item.MediaSource?.LiveStreamId; - list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty)); + if (!string.IsNullOrEmpty(accessToken)) + { + sb.Append("&ApiKey="); + sb.Append(accessToken); + } - list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : string.Empty)); + var liveStreamId = MediaSource?.LiveStreamId; + if (!string.IsNullOrEmpty(liveStreamId)) + { + sb.Append("&LiveStreamId="); + sb.Append(liveStreamId); + } - if (!item.IsDirectStream) + if (!IsDirectStream) { - if (item.RequireNonAnamorphic) + if (RequireNonAnamorphic) { - list.Add(new NameValuePair("RequireNonAnamorphic", item.RequireNonAnamorphic.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + sb.Append("&RequireNonAnamorphic="); + sb.Append(RequireNonAnamorphic.ToString(CultureInfo.InvariantCulture)); } - list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? item.TranscodingMaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); - - if (item.EnableSubtitlesInManifest) + if (TranscodingMaxAudioChannels.HasValue) { - list.Add(new NameValuePair("EnableSubtitlesInManifest", item.EnableSubtitlesInManifest.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + sb.Append("&TranscodingMaxAudioChannels="); + sb.Append(TranscodingMaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture)); } - if (item.EnableMpegtsM2TsMode) + if (EnableSubtitlesInManifest) { - list.Add(new NameValuePair("EnableMpegtsM2TsMode", item.EnableMpegtsM2TsMode.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + sb.Append("&EnableSubtitlesInManifest="); + sb.Append(EnableSubtitlesInManifest.ToString(CultureInfo.InvariantCulture)); } - if (item.EstimateContentLength) + if (EnableMpegtsM2TsMode) { - list.Add(new NameValuePair("EstimateContentLength", item.EstimateContentLength.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + sb.Append("&EnableMpegtsM2TsMode="); + sb.Append(EnableMpegtsM2TsMode.ToString(CultureInfo.InvariantCulture)); } - if (item.TranscodeSeekInfo != TranscodeSeekInfo.Auto) + if (EstimateContentLength) { - list.Add(new NameValuePair("TranscodeSeekInfo", item.TranscodeSeekInfo.ToString().ToLowerInvariant())); + sb.Append("&EstimateContentLength="); + sb.Append(EstimateContentLength.ToString(CultureInfo.InvariantCulture)); } - if (item.CopyTimestamps) + if (TranscodeSeekInfo != TranscodeSeekInfo.Auto) { - list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + sb.Append("&TranscodeSeekInfo="); + sb.Append(TranscodeSeekInfo.ToString()); } - list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); - - list.Add(new NameValuePair("EnableAudioVbrEncoding", item.EnableAudioVbrEncoding.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); - } - - list.Add(new NameValuePair("Tag", item.MediaSource?.ETag ?? string.Empty)); - - string subtitleCodecs = item.SubtitleCodecs.Count == 0 ? - string.Empty : - string.Join(",", item.SubtitleCodecs); - - list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty)); - - if (item.SubProtocol == MediaStreamProtocol.hls) - { - list.Add(new NameValuePair("SegmentContainer", item.Container ?? string.Empty)); + if (CopyTimestamps) + { + sb.Append("&CopyTimestamps="); + sb.Append(CopyTimestamps.ToString(CultureInfo.InvariantCulture)); + } - if (item.SegmentLength.HasValue) + if (RequireAvc) { - list.Add(new NameValuePair("SegmentLength", item.SegmentLength.Value.ToString(CultureInfo.InvariantCulture))); + sb.Append("&RequireAvc="); + sb.Append(RequireAvc.ToString(CultureInfo.InvariantCulture)); } - if (item.MinSegments.HasValue) + if (EnableAudioVbrEncoding) { - list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture))); + sb.Append("EnableAudioVbrEncoding="); + sb.Append(EnableAudioVbrEncoding.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); } + } - list.Add(new NameValuePair("BreakOnNonKeyFrames", item.BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture))); + var etag = MediaSource?.ETag; + if (!string.IsNullOrEmpty(etag)) + { + sb.Append("&Tag="); + sb.Append(etag); } - foreach (var pair in item.StreamOptions) + if (SubtitleStreamIndex.HasValue && SubtitleDeliveryMethod != SubtitleDeliveryMethod.External) { - if (string.IsNullOrEmpty(pair.Value)) - { - continue; - } + sb.Append("&SubtitleMethod="); + sb.AppendJoin(',', SubtitleDeliveryMethod); + } - // strip spaces to avoid having to encode h264 profile names - list.Add(new NameValuePair(pair.Key, pair.Value.Replace(" ", string.Empty, StringComparison.Ordinal))); + if (SubtitleStreamIndex.HasValue && SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed && SubtitleCodecs.Count != 0) + { + sb.Append("&SubtitleCodec="); + sb.AppendJoin(',', SubtitleCodecs); } - if (!item.IsDirectStream) + foreach (var pair in StreamOptions) { - list.Add(new NameValuePair("TranscodeReasons", item.TranscodeReasons.ToString())); + // Strip spaces to avoid having to encode h264 profile names + sb.Append('&'); + sb.Append(pair.Key); + sb.Append('='); + sb.Append(pair.Value.Replace(" ", string.Empty, StringComparison.Ordinal)); } - return list; + var transcodeReasonsValues = TranscodeReasons.GetUniqueFlags().ToArray(); + if (!IsDirectStream && transcodeReasonsValues.Length > 0) + { + sb.Append("&TranscodeReasons="); + sb.AppendJoin(',', transcodeReasonsValues); + } + + if (!string.IsNullOrEmpty(query)) + { + sb.Append(query); + } + + return sb.ToString(); } /// <summary> @@ -1189,7 +1271,7 @@ public class StreamInfo if (!string.IsNullOrEmpty(accessToken)) { - info.Url += "?api_key=" + accessToken; + info.Url += "?ApiKey=" + accessToken; } info.IsExternalUrl = false; diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs index 5a9fa22ae..5797d4250 100644 --- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs @@ -1,3 +1,4 @@ +using System; using System.ComponentModel; using System.Xml.Serialization; using Jellyfin.Data.Enums; @@ -6,6 +7,7 @@ namespace MediaBrowser.Model.Dlna; /// <summary> /// A class for transcoding profile information. +/// Note for client developers: Conditions defined in <see cref="CodecProfile"/> has higher priority and can override values defined here. /// </summary> public class TranscodingProfile { @@ -18,6 +20,33 @@ public class TranscodingProfile } /// <summary> + /// Initializes a new instance of the <see cref="TranscodingProfile" /> class copying the values from another instance. + /// </summary> + /// <param name="other">Another instance of <see cref="TranscodingProfile" /> to be copied.</param> + public TranscodingProfile(TranscodingProfile other) + { + ArgumentNullException.ThrowIfNull(other); + + Container = other.Container; + Type = other.Type; + VideoCodec = other.VideoCodec; + AudioCodec = other.AudioCodec; + Protocol = other.Protocol; + EstimateContentLength = other.EstimateContentLength; + EnableMpegtsM2TsMode = other.EnableMpegtsM2TsMode; + TranscodeSeekInfo = other.TranscodeSeekInfo; + CopyTimestamps = other.CopyTimestamps; + Context = other.Context; + EnableSubtitlesInManifest = other.EnableSubtitlesInManifest; + MaxAudioChannels = other.MaxAudioChannels; + MinSegments = other.MinSegments; + SegmentLength = other.SegmentLength; + BreakOnNonKeyFrames = other.BreakOnNonKeyFrames; + Conditions = other.Conditions; + EnableAudioVbrEncoding = other.EnableAudioVbrEncoding; + } + + /// <summary> /// Gets or sets the container. /// </summary> [XmlAttribute("container")] diff --git a/MediaBrowser.Model/Drawing/ImageFormatExtensions.cs b/MediaBrowser.Model/Drawing/ImageFormatExtensions.cs index 1c60ba460..53b9b1fad 100644 --- a/MediaBrowser.Model/Drawing/ImageFormatExtensions.cs +++ b/MediaBrowser.Model/Drawing/ImageFormatExtensions.cs @@ -17,12 +17,12 @@ public static class ImageFormatExtensions public static string GetMimeType(this ImageFormat format) => format switch { - ImageFormat.Bmp => "image/bmp", + ImageFormat.Bmp => MediaTypeNames.Image.Bmp, ImageFormat.Gif => MediaTypeNames.Image.Gif, ImageFormat.Jpg => MediaTypeNames.Image.Jpeg, - ImageFormat.Png => "image/png", - ImageFormat.Webp => "image/webp", - ImageFormat.Svg => "image/svg+xml", + ImageFormat.Png => MediaTypeNames.Image.Png, + ImageFormat.Webp => MediaTypeNames.Image.Webp, + ImageFormat.Svg => MediaTypeNames.Image.Svg, _ => throw new InvalidEnumArgumentException(nameof(format), (int)format, typeof(ImageFormat)) }; diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 7e8949e1f..7bfd8ca29 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; -using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Library; diff --git a/MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs b/MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs index 5963ed270..d481593cd 100644 --- a/MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs +++ b/MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs @@ -15,13 +15,13 @@ public class ClientCapabilitiesDto /// <summary> /// Gets or sets the list of playable media types. /// </summary> - [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] public IReadOnlyList<MediaType> PlayableMediaTypes { get; set; } = []; /// <summary> /// Gets or sets the list of supported commands. /// </summary> - [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] public IReadOnlyList<GeneralCommandType> SupportedCommands { get; set; } = []; /// <summary> diff --git a/MediaBrowser.Model/Dto/DisplayPreferencesDto.cs b/MediaBrowser.Model/Dto/DisplayPreferencesDto.cs index 90163ae91..54cbe65f6 100644 --- a/MediaBrowser.Model/Dto/DisplayPreferencesDto.cs +++ b/MediaBrowser.Model/Dto/DisplayPreferencesDto.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; namespace MediaBrowser.Model.Dto { diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index eff2e09da..66de18cfe 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -1,12 +1,10 @@ #nullable disable #pragma warning disable CS1591 -using System; using System.Collections.Generic; using System.ComponentModel; using System.Text.Json.Serialization; using Jellyfin.Data.Enums; -using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Session; @@ -17,10 +15,10 @@ namespace MediaBrowser.Model.Dto { public MediaSourceInfo() { - Formats = Array.Empty<string>(); - MediaStreams = Array.Empty<MediaStream>(); - MediaAttachments = Array.Empty<MediaAttachment>(); - RequiredHttpHeaders = new Dictionary<string, string>(); + Formats = []; + MediaStreams = []; + MediaAttachments = []; + RequiredHttpHeaders = []; SupportsTranscoding = true; SupportsDirectStream = true; SupportsDirectPlay = true; diff --git a/MediaBrowser.Model/Dto/SessionInfoDto.cs b/MediaBrowser.Model/Dto/SessionInfoDto.cs index 2496c933a..d727cd874 100644 --- a/MediaBrowser.Model/Dto/SessionInfoDto.cs +++ b/MediaBrowser.Model/Dto/SessionInfoDto.cs @@ -163,7 +163,7 @@ public class SessionInfoDto /// <summary> /// Gets or sets the playlist item id. /// </summary> - /// <value>The splaylist item id.</value> + /// <value>The playlist item id.</value> public string? PlaylistItemId { get; set; } /// <summary> diff --git a/MediaBrowser.Model/Entities/HardwareAccelerationType.cs b/MediaBrowser.Model/Entities/HardwareAccelerationType.cs index 198a2e00f..ece18ec3e 100644 --- a/MediaBrowser.Model/Entities/HardwareAccelerationType.cs +++ b/MediaBrowser.Model/Entities/HardwareAccelerationType.cs @@ -8,7 +8,7 @@ namespace MediaBrowser.Model.Entities; public enum HardwareAccelerationType { /// <summary> - /// Software accelleration. + /// Software acceleration. /// </summary> none = 0, diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 85c1f797b..400768ef3 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -383,7 +383,7 @@ namespace MediaBrowser.Model.Entities attributes.Add(string.IsNullOrEmpty(LocalizedUndefined) ? "Und" : LocalizedUndefined); } - if (IsHearingImpaired) + if (IsHearingImpaired == true) { attributes.Add(string.IsNullOrEmpty(LocalizedHearingImpaired) ? "Hearing Impaired" : LocalizedHearingImpaired); } @@ -537,7 +537,7 @@ namespace MediaBrowser.Model.Entities get { // In some cases AverageFrameRate for videos will be read as 1000fps even if it is not. - // This is probably due to a library compatability issue. + // This is probably due to a library compatibility issue. // See https://github.com/jellyfin/jellyfin/pull/12603#discussion_r1748044018 for more info. return AverageFrameRate < 1000 ? AverageFrameRate : RealFrameRate; } diff --git a/MediaBrowser.Model/Entities/MetadataProvider.cs b/MediaBrowser.Model/Entities/MetadataProvider.cs index bd8db9941..65337b60f 100644 --- a/MediaBrowser.Model/Entities/MetadataProvider.cs +++ b/MediaBrowser.Model/Entities/MetadataProvider.cs @@ -27,7 +27,7 @@ namespace MediaBrowser.Model.Entities Tvdb = 4, /// <summary> - /// The tvcom providerd. + /// The tvcom provider. /// </summary> Tvcom = 5, @@ -84,6 +84,11 @@ namespace MediaBrowser.Model.Entities /// <summary> /// The TvMaze provider. /// </summary> - TvMaze = 19 + TvMaze = 19, + + /// <summary> + /// The MusicBrainz recording provider. + /// </summary> + MusicBrainzRecording = 20, } } diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs index 479ec7712..385a86d31 100644 --- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs +++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Model.Entities; public static class ProviderIdsExtensions { /// <summary> - /// Case insensitive dictionary of <see cref="MetadataProvider"/> string representation. + /// Case-insensitive dictionary of <see cref="MetadataProvider"/> string representation. /// </summary> private static readonly Dictionary<string, string> _metadataProviderEnumDictionary = Enum.GetValues<MetadataProvider>() @@ -107,7 +107,7 @@ public static class ProviderIdsExtensions /// <param name="instance">The instance.</param> /// <param name="name">The name, this should not contain a '=' character.</param> /// <param name="value">The value.</param> - /// <remarks>Due to how deserialization from the database works the name can not contain '='.</remarks> + /// <remarks>Due to how deserialization from the database works the name cannot contain '='.</remarks> /// <returns><c>true</c> if the provider id got set successfully; otherwise, <c>false</c>.</returns> public static bool TrySetProviderId(this IHasProviderIds instance, string? name, string? value) { @@ -153,7 +153,7 @@ public static class ProviderIdsExtensions /// <param name="instance">The instance.</param> /// <param name="name">The name, this should not contain a '=' character.</param> /// <param name="value">The value.</param> - /// <remarks>Due to how deserialization from the database works the name can not contain '='.</remarks> + /// <remarks>Due to how deserialization from the database works the name cannot contain '='.</remarks> public static void SetProviderId(this IHasProviderIds instance, string name, string value) { ArgumentNullException.ThrowIfNull(instance); diff --git a/MediaBrowser.Model/Extensions/ContainerHelper.cs b/MediaBrowser.Model/Extensions/ContainerHelper.cs index c86328ba6..39e5358ba 100644 --- a/MediaBrowser.Model/Extensions/ContainerHelper.cs +++ b/MediaBrowser.Model/Extensions/ContainerHelper.cs @@ -14,7 +14,8 @@ public static class ContainerHelper /// in <paramref name="profileContainers"/>. /// </summary> /// <param name="profileContainers">The comma-delimited string being searched. - /// If the parameter begins with the <c>-</c> character, the operation is reversed.</param> + /// If the parameter begins with the <c>-</c> character, the operation is reversed. + /// If the parameter is empty or null, all containers in <paramref name="inputContainer"/> will be accepted.</param> /// <param name="inputContainer">The comma-delimited string being matched.</param> /// <returns>The result of the operation.</returns> public static bool ContainsContainer(string? profileContainers, string? inputContainer) @@ -34,7 +35,8 @@ public static class ContainerHelper /// in <paramref name="profileContainers"/>. /// </summary> /// <param name="profileContainers">The comma-delimited string being searched. - /// If the parameter begins with the <c>-</c> character, the operation is reversed.</param> + /// If the parameter begins with the <c>-</c> character, the operation is reversed. + /// If the parameter is empty or null, all containers in <paramref name="inputContainer"/> will be accepted.</param> /// <param name="inputContainer">The comma-delimited string being matched.</param> /// <returns>The result of the operation.</returns> public static bool ContainsContainer(string? profileContainers, ReadOnlySpan<char> inputContainer) @@ -53,7 +55,8 @@ public static class ContainerHelper /// Compares two containers, returning <paramref name="isNegativeList"/> if an item in <paramref name="inputContainer"/> /// does not exist in <paramref name="profileContainers"/>. /// </summary> - /// <param name="profileContainers">The comma-delimited string being searched.</param> + /// <param name="profileContainers">The comma-delimited string being searched. + /// If the parameter is empty or null, all containers in <paramref name="inputContainer"/> will be accepted.</param> /// <param name="isNegativeList">The boolean result to return if a match is not found.</param> /// <param name="inputContainer">The comma-delimited string being matched.</param> /// <returns>The result of the operation.</returns> @@ -71,7 +74,8 @@ public static class ContainerHelper /// Compares two containers, returning <paramref name="isNegativeList"/> if an item in <paramref name="inputContainer"/> /// does not exist in <paramref name="profileContainers"/>. /// </summary> - /// <param name="profileContainers">The comma-delimited string being searched.</param> + /// <param name="profileContainers">The comma-delimited string being searched. + /// If the parameter is empty or null, all containers in <paramref name="inputContainer"/> will be accepted.</param> /// <param name="isNegativeList">The boolean result to return if a match is not found.</param> /// <param name="inputContainer">The comma-delimited string being matched.</param> /// <returns>The result of the operation.</returns> @@ -106,7 +110,8 @@ public static class ContainerHelper /// Compares two containers, returning <paramref name="isNegativeList"/> if an item in <paramref name="inputContainer"/> /// does not exist in <paramref name="profileContainers"/>. /// </summary> - /// <param name="profileContainers">The profile containers being matched searched.</param> + /// <param name="profileContainers">The profile containers being matched searched. + /// If the parameter is empty or null, all containers in <paramref name="inputContainer"/> will be accepted.</param> /// <param name="isNegativeList">The boolean result to return if a match is not found.</param> /// <param name="inputContainer">The comma-delimited string being matched.</param> /// <returns>The result of the operation.</returns> diff --git a/MediaBrowser.Model/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs index 02a29e7fa..20deaa505 100644 --- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs +++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs @@ -52,7 +52,7 @@ namespace MediaBrowser.Model.Globalization /// <summary> /// Gets the localization options. /// </summary> - /// <returns><see cref="IEnumerable{LocalizatonOption}" />.</returns> + /// <returns><see cref="IEnumerable{LocalizationOption}" />.</returns> IEnumerable<LocalizationOption> GetLocalizationOptions(); /// <summary> diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index 2085328dd..0ed2e30d5 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -145,7 +145,7 @@ namespace MediaBrowser.Model.IO /// Gets the directories. /// </summary> /// <param name="path">The path.</param> - /// <param name="recursive">If set to <c>true</c> also searches in subdirectiories.</param> + /// <param name="recursive">If set to <c>true</c> also searches in subdirectories.</param> /// <returns>All found directories.</returns> IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false); @@ -153,13 +153,41 @@ namespace MediaBrowser.Model.IO /// Gets the files. /// </summary> /// <param name="path">The path in which to search.</param> - /// <param name="recursive">If set to <c>true</c> also searches in subdirectiories.</param> + /// <param name="recursive">If set to <c>true</c> also searches in subdirectories.</param> /// <returns>All found files.</returns> IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false); + /// <summary> + /// Gets the files. + /// </summary> + /// <param name="path">The path in which to search.</param> + /// <param name="searchPattern">The search string to match against the names of files. This parameter can contain a combination of valid literal path and wildcard (* and ?) characters, but it doesn't support regular expressions.</param> + /// <param name="recursive">If set to <c>true</c> also searches in subdirectories.</param> + /// <returns>All found files.</returns> + IEnumerable<FileSystemMetadata> GetFiles(string path, string searchPattern, bool recursive = false); + + /// <summary> + /// Gets the files. + /// </summary> + /// <param name="path">The path in which to search.</param> + /// <param name="extensions">The file extensions to search for.</param> + /// <param name="enableCaseSensitiveExtensions">Enable case-sensitive check for extensions.</param> + /// <param name="recursive">If set to <c>true</c> also searches in subdirectories.</param> + /// <returns>All found files.</returns> IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string>? extensions, bool enableCaseSensitiveExtensions, bool recursive); /// <summary> + /// Gets the files. + /// </summary> + /// <param name="path">The path in which to search.</param> + /// <param name="searchPattern">The search string to match against the names of files. This parameter can contain a combination of valid literal path and wildcard (* and ?) characters, but it doesn't support regular expressions.</param> + /// <param name="extensions">The file extensions to search for.</param> + /// <param name="enableCaseSensitiveExtensions">Enable case-sensitive check for extensions.</param> + /// <param name="recursive">If set to <c>true</c> also searches in subdirectories.</param> + /// <returns>All found files.</returns> + IEnumerable<FileSystemMetadata> GetFiles(string path, string searchPattern, IReadOnlyList<string>? extensions, bool enableCaseSensitiveExtensions, bool recursive); + + /// <summary> /// Gets the file system entries. /// </summary> /// <param name="path">The path.</param> diff --git a/MediaBrowser.Model/Library/UserViewQuery.cs b/MediaBrowser.Model/Library/UserViewQuery.cs index 643a1f9b1..01d5e3b6c 100644 --- a/MediaBrowser.Model/Library/UserViewQuery.cs +++ b/MediaBrowser.Model/Library/UserViewQuery.cs @@ -1,8 +1,8 @@ #pragma warning disable CS1591 using System; -using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; namespace MediaBrowser.Model.Library { diff --git a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs index d872572b7..38e273176 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs @@ -3,6 +3,7 @@ using System; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; namespace MediaBrowser.Model.LiveTv { diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs b/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs index dae885775..e93ad81d3 100644 --- a/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs +++ b/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs @@ -1,6 +1,6 @@ #pragma warning disable CS1591 -using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; namespace MediaBrowser.Model.LiveTv { diff --git a/MediaBrowser.Model/MediaSegments/MediaSegmentDto.cs b/MediaBrowser.Model/MediaSegments/MediaSegmentDto.cs index a0433fee1..6e5c7885c 100644 --- a/MediaBrowser.Model/MediaSegments/MediaSegmentDto.cs +++ b/MediaBrowser.Model/MediaSegments/MediaSegmentDto.cs @@ -1,5 +1,5 @@ using System; -using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; namespace MediaBrowser.Model.MediaSegments; diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs index e4c0239b8..de087d0e7 100644 --- a/MediaBrowser.Model/Net/MimeTypes.cs +++ b/MediaBrowser.Model/Net/MimeTypes.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Net.Mime; using Jellyfin.Extensions; namespace MediaBrowser.Model.Net @@ -144,7 +145,7 @@ namespace MediaBrowser.Model.Net new("video/x-matroska", ".mkv"), }.ToFrozenDictionary(pair => pair.Key, pair => pair.Value, StringComparer.OrdinalIgnoreCase); - public static string GetMimeType(string path) => GetMimeType(path, "application/octet-stream"); + public static string GetMimeType(string path) => GetMimeType(path, MediaTypeNames.Application.Octet); /// <summary> /// Gets the type of the MIME. diff --git a/MediaBrowser.Model/Plugins/PluginStatus.cs b/MediaBrowser.Model/Plugins/PluginStatus.cs index bd420d7b4..9c7a8f0c2 100644 --- a/MediaBrowser.Model/Plugins/PluginStatus.cs +++ b/MediaBrowser.Model/Plugins/PluginStatus.cs @@ -34,7 +34,12 @@ namespace MediaBrowser.Model.Plugins Malfunctioned = -3, /// <summary> - /// This plugin has been superceded by another version. + /// This plugin has been superseded by another version. + /// </summary> + Superseded = -4, + + /// <summary> + /// [DEPRECATED] See Superseded. /// </summary> Superceded = -4, diff --git a/MediaBrowser.Model/Providers/ExternalIdInfo.cs b/MediaBrowser.Model/Providers/ExternalIdInfo.cs index 1f5163aa8..e7a309924 100644 --- a/MediaBrowser.Model/Providers/ExternalIdInfo.cs +++ b/MediaBrowser.Model/Providers/ExternalIdInfo.cs @@ -1,5 +1,3 @@ -using System; - namespace MediaBrowser.Model.Providers { /// <summary> @@ -13,15 +11,11 @@ namespace MediaBrowser.Model.Providers /// <param name="name">Name of the external id provider (IE: IMDB, MusicBrainz, etc).</param> /// <param name="key">Key for this id. This key should be unique across all providers.</param> /// <param name="type">Specific media type for this id.</param> - /// <param name="urlFormatString">URL format string.</param> - public ExternalIdInfo(string name, string key, ExternalIdMediaType? type, string? urlFormatString) + public ExternalIdInfo(string name, string key, ExternalIdMediaType? type) { Name = name; Key = key; Type = type; -#pragma warning disable CS0618 // Type or member is obsolete - Remove 10.11 - UrlFormatString = urlFormatString; -#pragma warning restore CS0618 // Type or member is obsolete } /// <summary> @@ -46,11 +40,5 @@ namespace MediaBrowser.Model.Providers /// This can be used along with the <see cref="Name"/> to localize the external id on the client. /// </remarks> public ExternalIdMediaType? Type { get; set; } - - /// <summary> - /// Gets or sets the URL format string. - /// </summary> - [Obsolete("Obsolete in 10.10, to be removed in 10.11")] - public string? UrlFormatString { get; set; } } } diff --git a/MediaBrowser.Model/Providers/ExternalIdMediaType.cs b/MediaBrowser.Model/Providers/ExternalIdMediaType.cs index ef518369c..71a131bb8 100644 --- a/MediaBrowser.Model/Providers/ExternalIdMediaType.cs +++ b/MediaBrowser.Model/Providers/ExternalIdMediaType.cs @@ -71,6 +71,11 @@ namespace MediaBrowser.Model.Providers /// <summary> /// A book. /// </summary> - Book = 13 + Book = 13, + + /// <summary> + /// A music recording. + /// </summary> + Recording = 14 } } diff --git a/MediaBrowser.Model/Querying/LatestItemsQuery.cs b/MediaBrowser.Model/Querying/LatestItemsQuery.cs index 251ff5d68..40dc81397 100644 --- a/MediaBrowser.Model/Querying/LatestItemsQuery.cs +++ b/MediaBrowser.Model/Querying/LatestItemsQuery.cs @@ -2,8 +2,8 @@ #pragma warning disable CS1591 using System; -using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; using MediaBrowser.Model.Entities; namespace MediaBrowser.Model.Querying diff --git a/MediaBrowser.Model/Querying/NextUpQuery.cs b/MediaBrowser.Model/Querying/NextUpQuery.cs index 8dece28a0..a2a3a9d1b 100644 --- a/MediaBrowser.Model/Querying/NextUpQuery.cs +++ b/MediaBrowser.Model/Querying/NextUpQuery.cs @@ -1,79 +1,72 @@ #pragma warning disable CS1591 using System; -using Jellyfin.Data.Entities; +using Jellyfin.Database.Implementations.Entities; using MediaBrowser.Model.Entities; -namespace MediaBrowser.Model.Querying +namespace MediaBrowser.Model.Querying; + +public class NextUpQuery { - public class NextUpQuery + public NextUpQuery() { - public NextUpQuery() - { - EnableImageTypes = Array.Empty<ImageType>(); - EnableTotalRecordCount = true; - DisableFirstEpisode = false; - NextUpDateCutoff = DateTime.MinValue; - EnableResumable = false; - EnableRewatching = false; - } - - /// <summary> - /// Gets or sets the user. - /// </summary> - /// <value>The user.</value> - public required User User { get; set; } + EnableImageTypes = Array.Empty<ImageType>(); + EnableTotalRecordCount = true; + NextUpDateCutoff = DateTime.MinValue; + EnableResumable = false; + EnableRewatching = false; + } - /// <summary> - /// Gets or sets the parent identifier. - /// </summary> - /// <value>The parent identifier.</value> - public Guid? ParentId { get; set; } + /// <summary> + /// Gets or sets the user. + /// </summary> + /// <value>The user.</value> + public required User User { get; set; } - /// <summary> - /// Gets or sets the series id. - /// </summary> - /// <value>The series id.</value> - public Guid? SeriesId { get; set; } + /// <summary> + /// Gets or sets the parent identifier. + /// </summary> + /// <value>The parent identifier.</value> + public Guid? ParentId { get; set; } - /// <summary> - /// Gets or sets the start index. Use for paging. - /// </summary> - /// <value>The start index.</value> - public int? StartIndex { get; set; } + /// <summary> + /// Gets or sets the series id. + /// </summary> + /// <value>The series id.</value> + public Guid? SeriesId { get; set; } - /// <summary> - /// Gets or sets the maximum number of items to return. - /// </summary> - /// <value>The limit.</value> - public int? Limit { get; set; } + /// <summary> + /// Gets or sets the start index. Use for paging. + /// </summary> + /// <value>The start index.</value> + public int? StartIndex { get; set; } - /// <summary> - /// Gets or sets the enable image types. - /// </summary> - /// <value>The enable image types.</value> - public ImageType[] EnableImageTypes { get; set; } + /// <summary> + /// Gets or sets the maximum number of items to return. + /// </summary> + /// <value>The limit.</value> + public int? Limit { get; set; } - public bool EnableTotalRecordCount { get; set; } + /// <summary> + /// Gets or sets the enable image types. + /// </summary> + /// <value>The enable image types.</value> + public ImageType[] EnableImageTypes { get; set; } - /// <summary> - /// Gets or sets a value indicating whether do disable sending first episode as next up. - /// </summary> - public bool DisableFirstEpisode { get; set; } + public bool EnableTotalRecordCount { get; set; } - /// <summary> - /// Gets or sets a value indicating the oldest date for a show to appear in Next Up. - /// </summary> - public DateTime NextUpDateCutoff { get; set; } + /// <summary> + /// Gets or sets a value indicating the oldest date for a show to appear in Next Up. + /// </summary> + public DateTime NextUpDateCutoff { get; set; } - /// <summary> - /// Gets or sets a value indicating whether to include resumable episodes as next up. - /// </summary> - public bool EnableResumable { get; set; } + /// <summary> + /// Gets or sets a value indicating whether to include resumable episodes as next up. + /// </summary> + public bool EnableResumable { get; set; } - /// <summary> - /// Gets or sets a value indicating whether getting rewatching next up list. - /// </summary> - public bool EnableRewatching { get; set; } - } + /// <summary> + /// Gets or sets a value indicating whether getting rewatching next up list. + /// </summary> + public bool EnableRewatching { get; set; } } diff --git a/MediaBrowser.Model/Session/TranscodeReason.cs b/MediaBrowser.Model/Session/TranscodeReason.cs index 39c5ac8fa..902bab9a6 100644 --- a/MediaBrowser.Model/Session/TranscodeReason.cs +++ b/MediaBrowser.Model/Session/TranscodeReason.cs @@ -14,6 +14,7 @@ namespace MediaBrowser.Model.Session SubtitleCodecNotSupported = 1 << 3, AudioIsExternal = 1 << 4, SecondaryAudioNotSupported = 1 << 5, + StreamCountExceedsLimit = 1 << 26, // Video Constraints VideoProfileNotSupported = 1 << 6, diff --git a/MediaBrowser.Model/Session/TranscodingInfo.cs b/MediaBrowser.Model/Session/TranscodingInfo.cs index ae25267ac..11e83844b 100644 --- a/MediaBrowser.Model/Session/TranscodingInfo.cs +++ b/MediaBrowser.Model/Session/TranscodingInfo.cs @@ -5,7 +5,7 @@ using MediaBrowser.Model.Entities; namespace MediaBrowser.Model.Session; /// <summary> -/// Class holding information on a runnning transcode. +/// Class holding information on a running transcode. /// </summary> public class TranscodingInfo { diff --git a/MediaBrowser.Model/System/PublicSystemInfo.cs b/MediaBrowser.Model/System/PublicSystemInfo.cs index 31a895642..c26cfb667 100644 --- a/MediaBrowser.Model/System/PublicSystemInfo.cs +++ b/MediaBrowser.Model/System/PublicSystemInfo.cs @@ -47,7 +47,7 @@ namespace MediaBrowser.Model.System /// Gets or sets a value indicating whether the startup wizard is completed. /// </summary> /// <remarks> - /// Nullable for OpenAPI specification only to retain backwards compatibility in apiclients. + /// Nullable for OpenAPI specification only to retain backwards compatibility in api clients. /// </remarks> /// <value>The startup completion status.</value>] public bool? StartupWizardCompleted { get; set; } diff --git a/MediaBrowser.Model/System/WakeOnLanInfo.cs b/MediaBrowser.Model/System/WakeOnLanInfo.cs deleted file mode 100644 index aba19a6ba..000000000 --- a/MediaBrowser.Model/System/WakeOnLanInfo.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Net.NetworkInformation; - -namespace MediaBrowser.Model.System -{ - /// <summary> - /// Provides the MAC address and port for wake-on-LAN functionality. - /// </summary> - public class WakeOnLanInfo - { - /// <summary> - /// Initializes a new instance of the <see cref="WakeOnLanInfo" /> class. - /// </summary> - /// <param name="macAddress">The MAC address.</param> - public WakeOnLanInfo(PhysicalAddress macAddress) : this(macAddress.ToString()) - { - } - - /// <summary> - /// Initializes a new instance of the <see cref="WakeOnLanInfo" /> class. - /// </summary> - /// <param name="macAddress">The MAC address.</param> - public WakeOnLanInfo(string macAddress) : this() - { - MacAddress = macAddress; - } - - /// <summary> - /// Initializes a new instance of the <see cref="WakeOnLanInfo" /> class. - /// </summary> - public WakeOnLanInfo() - { - Port = 9; - } - - /// <summary> - /// Gets the MAC address of the device. - /// </summary> - /// <value>The MAC address.</value> - public string? MacAddress { get; } - - /// <summary> - /// Gets or sets the wake-on-LAN port. - /// </summary> - /// <value>The wake-on-LAN port.</value> - public int Port { get; set; } - } -} diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index 951e05763..3d430e101 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -6,7 +6,8 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Xml.Serialization; using Jellyfin.Data.Enums; -using AccessSchedule = Jellyfin.Data.Entities.AccessSchedule; +using Jellyfin.Database.Implementations.Enums; +using AccessSchedule = Jellyfin.Database.Implementations.Entities.AccessSchedule; namespace MediaBrowser.Model.Users { |
