diff options
Diffstat (limited to 'MediaBrowser.Model')
32 files changed, 722 insertions, 194 deletions
diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index d67a2479f..2720c0bdf 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -46,6 +46,8 @@ public class EncodingOptions DeinterlaceMethod = DeinterlaceMethod.yadif; EnableDecodingColorDepth10Hevc = true; EnableDecodingColorDepth10Vp9 = true; + EnableDecodingColorDepth10HevcRext = false; + EnableDecodingColorDepth12HevcRext = false; // Enhanced Nvdec or system native decoder is required for DoVi to SDR tone-mapping. EnableEnhancedNvdecDecoder = true; PreferSystemNativeHwDecoder = true; @@ -235,6 +237,16 @@ public class EncodingOptions public bool EnableDecodingColorDepth10Vp9 { get; set; } /// <summary> + /// Gets or sets a value indicating whether 8/10bit HEVC RExt decoding is enabled. + /// </summary> + public bool EnableDecodingColorDepth10HevcRext { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether 12bit HEVC RExt decoding is enabled. + /// </summary> + public bool EnableDecodingColorDepth12HevcRext { get; set; } + + /// <summary> /// Gets or sets a value indicating whether the enhanced NVDEC is enabled. /// </summary> public bool EnableEnhancedNvdecDecoder { get; set; } diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index 04283cc9e..590b74304 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -2,19 +2,20 @@ using System; using System.ComponentModel; +using System.Linq; namespace MediaBrowser.Model.Configuration { public class LibraryOptions { - private static readonly char[] _defaultTagDelimiters = ['/', '|', ';', '\\']; + private static readonly string[] _defaultTagDelimiters = ["/", "|", ";", "\\"]; public LibraryOptions() { TypeOptions = Array.Empty<TypeOptions>(); DisabledSubtitleFetchers = Array.Empty<string>(); DisabledMediaSegmentProviders = Array.Empty<string>(); - MediaSegmentProvideOrder = Array.Empty<string>(); + MediaSegmentProviderOrder = Array.Empty<string>(); SubtitleFetcherOrder = Array.Empty<string>(); DisabledLocalMetadataReaders = Array.Empty<string>(); DisabledLyricFetchers = Array.Empty<string>(); @@ -98,7 +99,7 @@ namespace MediaBrowser.Model.Configuration public string[] DisabledMediaSegmentProviders { get; set; } - public string[] MediaSegmentProvideOrder { get; set; } + public string[] MediaSegmentProviderOrder { get; set; } public bool SkipSubtitlesIfEmbeddedSubtitlesPresent { get; set; } @@ -126,8 +127,7 @@ namespace MediaBrowser.Model.Configuration [DefaultValue(false)] public bool UseCustomTagDelimiters { get; set; } - [DefaultValue(typeof(LibraryOptions), nameof(_defaultTagDelimiters))] - public char[] CustomTagDelimiters { get; set; } + public string[] CustomTagDelimiters { get; set; } public string[] DelimiterWhitelist { get; set; } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 5ad588200..693bf90e7 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; @@ -244,17 +244,12 @@ public class ServerConfiguration : BaseApplicationConfiguration public int LibraryMetadataRefreshConcurrency { get; set; } /// <summary> - /// Gets or sets a value indicating whether older plugins should automatically be deleted from the plugin folder. - /// </summary> - public bool RemoveOldPlugins { get; set; } - - /// <summary> /// Gets or sets a value indicating whether clients should be allowed to upload logs. /// </summary> 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; } @@ -281,4 +276,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/Devices/DeviceInfo.cs b/MediaBrowser.Model/Devices/DeviceInfo.cs index 4962992a0..115598613 100644 --- a/MediaBrowser.Model/Devices/DeviceInfo.cs +++ b/MediaBrowser.Model/Devices/DeviceInfo.cs @@ -1,69 +1,84 @@ -#nullable disable -#pragma warning disable CS1591 - using System; using MediaBrowser.Model.Session; -namespace MediaBrowser.Model.Devices +namespace MediaBrowser.Model.Devices; + +/// <summary> +/// A class for device Information. +/// </summary> +public class DeviceInfo { - public class DeviceInfo + /// <summary> + /// Initializes a new instance of the <see cref="DeviceInfo"/> class. + /// </summary> + public DeviceInfo() { - public DeviceInfo() - { - Capabilities = new ClientCapabilities(); - } + Capabilities = new ClientCapabilities(); + } - public string Name { get; set; } + /// <summary> + /// Gets or sets the name. + /// </summary> + /// <value>The name.</value> + public string? Name { get; set; } - public string CustomName { get; set; } + /// <summary> + /// Gets or sets the custom name. + /// </summary> + /// <value>The custom name.</value> + public string? CustomName { get; set; } - /// <summary> - /// Gets or sets the access token. - /// </summary> - public string AccessToken { get; set; } + /// <summary> + /// Gets or sets the access token. + /// </summary> + /// <value>The access token.</value> + public string? AccessToken { get; set; } - /// <summary> - /// Gets or sets the identifier. - /// </summary> - /// <value>The identifier.</value> - public string Id { get; set; } + /// <summary> + /// Gets or sets the identifier. + /// </summary> + /// <value>The identifier.</value> + public string? Id { get; set; } - /// <summary> - /// Gets or sets the last name of the user. - /// </summary> - /// <value>The last name of the user.</value> - public string LastUserName { get; set; } + /// <summary> + /// Gets or sets the last name of the user. + /// </summary> + /// <value>The last name of the user.</value> + public string? LastUserName { get; set; } - /// <summary> - /// Gets or sets the name of the application. - /// </summary> - /// <value>The name of the application.</value> - public string AppName { get; set; } + /// <summary> + /// Gets or sets the name of the application. + /// </summary> + /// <value>The name of the application.</value> + public string? AppName { get; set; } - /// <summary> - /// Gets or sets the application version. - /// </summary> - /// <value>The application version.</value> - public string AppVersion { get; set; } + /// <summary> + /// Gets or sets the application version. + /// </summary> + /// <value>The application version.</value> + public string? AppVersion { get; set; } - /// <summary> - /// Gets or sets the last user identifier. - /// </summary> - /// <value>The last user identifier.</value> - public Guid LastUserId { get; set; } + /// <summary> + /// Gets or sets the last user identifier. + /// </summary> + /// <value>The last user identifier.</value> + public Guid? LastUserId { get; set; } - /// <summary> - /// Gets or sets the date last modified. - /// </summary> - /// <value>The date last modified.</value> - public DateTime DateLastActivity { get; set; } + /// <summary> + /// Gets or sets the date last modified. + /// </summary> + /// <value>The date last modified.</value> + public DateTime? DateLastActivity { get; set; } - /// <summary> - /// Gets or sets the capabilities. - /// </summary> - /// <value>The capabilities.</value> - public ClientCapabilities Capabilities { get; set; } + /// <summary> + /// Gets or sets the capabilities. + /// </summary> + /// <value>The capabilities.</value> + public ClientCapabilities Capabilities { get; set; } - public string IconUrl { get; set; } - } + /// <summary> + /// Gets or sets the icon URL. + /// </summary> + /// <value>The icon URL.</value> + public string? IconUrl { get; set; } } diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs index af0787990..1b046f54e 100644 --- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs +++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs @@ -25,8 +25,8 @@ 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="numVideoStreams">The number of video streams.</param> /// <param name="numAudioStreams">The number of audio streams.</param> diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index f68957622..995b7633a 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs @@ -22,7 +22,7 @@ public class DeviceProfile /// <summary> /// Gets or sets the unique internal identifier. /// </summary> - public Guid Id { get; set; } + public Guid? Id { get; set; } /// <summary> /// Gets or sets the maximum allowed bitrate for all streamed content. 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/ResolutionNormalizer.cs b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs index 5d7daa81a..1a636b240 100644 --- a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs +++ b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs @@ -2,28 +2,33 @@ #pragma warning disable CS1591 using System; +using System.Linq; namespace MediaBrowser.Model.Dlna { public static class ResolutionNormalizer { - private static readonly ResolutionConfiguration[] Configurations = - new[] - { - new ResolutionConfiguration(426, 320000), - new ResolutionConfiguration(640, 400000), - new ResolutionConfiguration(720, 950000), - new ResolutionConfiguration(1280, 2500000), - new ResolutionConfiguration(1920, 4000000), - new ResolutionConfiguration(2560, 20000000), - new ResolutionConfiguration(3840, 35000000) - }; + // Please note: all bitrate here are in the scale of SDR h264 bitrate at 30fps + private static readonly ResolutionConfiguration[] _configurations = + [ + new ResolutionConfiguration(416, 365000), + new ResolutionConfiguration(640, 730000), + new ResolutionConfiguration(768, 1100000), + new ResolutionConfiguration(960, 3000000), + new ResolutionConfiguration(1280, 6000000), + new ResolutionConfiguration(1920, 13500000), + new ResolutionConfiguration(2560, 28000000), + new ResolutionConfiguration(3840, 50000000) + ]; public static ResolutionOptions Normalize( int? inputBitrate, int outputBitrate, + int h264EquivalentOutputBitrate, int? maxWidth, - int? maxHeight) + int? maxHeight, + float? targetFps, + bool isHdr = false) // We are not doing HDR transcoding for now, leave for future use { // If the bitrate isn't changing, then don't downscale the resolution if (inputBitrate.HasValue && outputBitrate >= inputBitrate.Value) @@ -38,16 +43,26 @@ namespace MediaBrowser.Model.Dlna } } - var resolutionConfig = GetResolutionConfiguration(outputBitrate); - if (resolutionConfig is not null) + var referenceBitrate = h264EquivalentOutputBitrate * (30.0f / (targetFps ?? 30.0f)); + + if (isHdr) { - var originvalValue = maxWidth; + referenceBitrate *= 0.8f; + } - maxWidth = Math.Min(resolutionConfig.MaxWidth, maxWidth ?? resolutionConfig.MaxWidth); - if (!originvalValue.HasValue || originvalValue.Value != maxWidth.Value) - { - maxHeight = null; - } + var resolutionConfig = GetResolutionConfiguration(Convert.ToInt32(referenceBitrate)); + + if (resolutionConfig is null) + { + return new ResolutionOptions { MaxWidth = maxWidth, MaxHeight = maxHeight }; + } + + var originWidthValue = maxWidth; + + maxWidth = Math.Min(resolutionConfig.MaxWidth, maxWidth ?? resolutionConfig.MaxWidth); + if (!originWidthValue.HasValue || originWidthValue.Value != maxWidth.Value) + { + maxHeight = null; } return new ResolutionOptions @@ -59,19 +74,7 @@ namespace MediaBrowser.Model.Dlna private static ResolutionConfiguration GetResolutionConfiguration(int outputBitrate) { - ResolutionConfiguration previousOption = null; - - foreach (var config in Configurations) - { - if (outputBitrate <= config.MaxBitrate) - { - return previousOption ?? config; - } - - previousOption = config; - } - - return null; + return _configurations.FirstOrDefault(config => outputBitrate <= config.MaxBitrate); } } } diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index bf122dcc7..1ed493708 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. @@ -65,7 +65,7 @@ namespace MediaBrowser.Model.Dlna if (streamInfo is not null) { streamInfo.DeviceId = options.DeviceId; - streamInfo.DeviceProfileId = options.Profile.Id.ToString("N", CultureInfo.InvariantCulture); + streamInfo.DeviceProfileId = options.Profile.Id?.ToString("N", CultureInfo.InvariantCulture); streams.Add(streamInfo); } } @@ -208,6 +208,14 @@ namespace MediaBrowser.Model.Dlna var longBitrate = Math.Min(transcodingBitrate, playlistItem.AudioBitrate ?? transcodingBitrate); playlistItem.AudioBitrate = longBitrate > int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate); + + // Pure audio transcoding does not support comma separated list of transcoding codec at the moment. + // So just use the AudioCodec as is would be safe enough as the _transcoderSupport.CanEncodeToAudioCodec + // would fail so this profile will not even be picked up. + if (playlistItem.AudioCodecs.Count == 0 && !string.IsNullOrWhiteSpace(transcodingProfile.AudioCodec)) + { + playlistItem.AudioCodecs = [transcodingProfile.AudioCodec]; + } } playlistItem.TranscodeReasons = transcodeReasons; @@ -240,7 +248,7 @@ namespace MediaBrowser.Model.Dlna foreach (var stream in streams) { stream.DeviceId = options.DeviceId; - stream.DeviceProfileId = options.Profile.Id.ToString("N", CultureInfo.InvariantCulture); + stream.DeviceProfileId = options.Profile.Id?.ToString("N", CultureInfo.InvariantCulture); } return GetOptimalStream(streams, options.GetMaxBitrate(false) ?? 0); @@ -389,9 +397,10 @@ namespace MediaBrowser.Model.Dlna /// <param name="type">The <see cref="DlnaProfileType"/>.</param> /// <param name="playProfile">The <see cref="DirectPlayProfile"/> object to get the video stream from.</param> /// <returns>The normalized input container.</returns> - public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile? profile, DlnaProfileType type, DirectPlayProfile? playProfile = null) + public static string? NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile? profile, DlnaProfileType type, DirectPlayProfile? playProfile = null) { - if (profile is null || !inputContainer.Contains(',', StringComparison.OrdinalIgnoreCase)) + // If the source is Live TV the inputContainer will be null until the mediasource is probed on first access + if (profile is null || string.IsNullOrEmpty(inputContainer) || !inputContainer.Contains(',', StringComparison.OrdinalIgnoreCase)) { return inputContainer; } @@ -639,7 +648,8 @@ namespace MediaBrowser.Model.Dlna RunTimeTicks = item.RunTimeTicks, Context = options.Context, DeviceProfile = options.Profile, - SubtitleStreamIndex = options.SubtitleStreamIndex ?? GetDefaultSubtitleStreamIndex(item, options.Profile.SubtitleProfiles) + SubtitleStreamIndex = options.SubtitleStreamIndex ?? GetDefaultSubtitleStreamIndex(item, options.Profile.SubtitleProfiles), + AlwaysBurnInSubtitleWhenTranscoding = options.AlwaysBurnInSubtitleWhenTranscoding }; var subtitleStream = playlistItem.SubtitleStreamIndex.HasValue ? item.GetMediaStream(MediaStreamType.Subtitle, playlistItem.SubtitleStreamIndex.Value) : null; @@ -767,20 +777,7 @@ namespace MediaBrowser.Model.Dlna if (subtitleStream is not null) { var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, _transcoderSupport, transcodingProfile.Container, transcodingProfile.Protocol); - - if (options.AlwaysBurnInSubtitleWhenTranscoding && (playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0) - { - playlistItem.SubtitleDeliveryMethod = SubtitleDeliveryMethod.Encode; - foreach (SubtitleProfile profile in options.Profile.SubtitleProfiles) - { - profile.Method = SubtitleDeliveryMethod.Encode; - } - } - else - { - playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method; - } - + playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method; playlistItem.SubtitleFormat = subtitleProfile.Format; playlistItem.SubtitleCodecs = [subtitleProfile.Format]; } @@ -865,18 +862,37 @@ namespace MediaBrowser.Model.Dlna if (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.ContainsAnyCodec(transcodingAudioCodec, 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 rankAudio = 3; + + if (conditionsSatisfied) + { + rankAudio = string.Equals(transcodingAudioCodec, audioCodec, StringComparison.OrdinalIgnoreCase) ? 1 : 2; + } + + rank.Audio = Math.Min(rank.Audio, rankAudio); + + if (rank.Audio == 1) + { + break; + } } } @@ -966,9 +982,28 @@ 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 directAudioStreamSatisfied = audioStreamWithSupportedCodec is not null && !channelsExceedsLimit + && options.Profile.CodecProfiles + .Where(i => i.Type == CodecType.VideoAudio + && i.ContainsAnyCodec(audioStreamWithSupportedCodec.Codec, container) + && i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioStreamWithSupportedCodec.Channels, audioStreamWithSupportedCodec.BitRate, audioStreamWithSupportedCodec.SampleRate, audioStreamWithSupportedCodec.BitDepth, audioStreamWithSupportedCodec.Profile, false))) + .Select(i => i.Conditions.All(condition => + { + var satisfied = ConditionProcessor.IsVideoAudioConditionSatisfied(condition, audioStreamWithSupportedCodec.Channels, audioStreamWithSupportedCodec.BitRate, audioStreamWithSupportedCodec.SampleRate, audioStreamWithSupportedCodec.BitDepth, audioStreamWithSupportedCodec.Profile, false); + if (!satisfied) + { + playlistItem.TranscodeReasons |= GetTranscodeReasonForFailedCondition(condition); + } - var channelsExceedsLimit = audioStreamWithSupportedCodec is not null && directAudioStream is null; + return satisfied; + })) + .All(satisfied => satisfied); + + directAudioStreamSatisfied = directAudioStreamSatisfied && !playlistItem.TranscodeReasons.HasFlag(TranscodeReason.ContainerBitrateExceedsLimit); + + var directAudioStream = directAudioStreamSatisfied ? audioStreamWithSupportedCodec : null; if (channelsExceedsLimit && playlistItem.TargetAudioStream is not null) { @@ -1090,12 +1125,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) @@ -2216,7 +2251,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)) @@ -2224,6 +2259,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)) @@ -2238,19 +2287,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) diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 3be686088..e44152213 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -271,6 +271,11 @@ public class StreamInfo public bool EnableAudioVbrEncoding { get; set; } /// <summary> + /// Gets or sets a value indicating whether always burn in subtitles when transcoding. + /// </summary> + public bool AlwaysBurnInSubtitleWhenTranscoding { get; set; } + + /// <summary> /// Gets a value indicating whether the stream is direct. /// </summary> public bool IsDirectStream => MediaSource?.VideoType is not (VideoType.Dvd or VideoType.BluRay) @@ -953,7 +958,7 @@ public class StreamInfo 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.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleStreamIndex.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)); @@ -974,7 +979,7 @@ public class StreamInfo } list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty)); - list.Add(new NameValuePair("api_key", accessToken ?? string.Empty)); + list.Add(new NameValuePair("ApiKey", accessToken ?? string.Empty)); string? liveStreamId = item.MediaSource?.LiveStreamId; list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty)); @@ -1184,7 +1189,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/ClientCapabilitiesDto.cs b/MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs new file mode 100644 index 000000000..5963ed270 --- /dev/null +++ b/MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; +using Jellyfin.Extensions.Json.Converters; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Session; + +namespace MediaBrowser.Model.Dto; + +/// <summary> +/// Client capabilities dto. +/// </summary> +public class ClientCapabilitiesDto +{ + /// <summary> + /// Gets or sets the list of playable media types. + /// </summary> + [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + public IReadOnlyList<MediaType> PlayableMediaTypes { get; set; } = []; + + /// <summary> + /// Gets or sets the list of supported commands. + /// </summary> + [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + public IReadOnlyList<GeneralCommandType> SupportedCommands { get; set; } = []; + + /// <summary> + /// Gets or sets a value indicating whether session supports media control. + /// </summary> + public bool SupportsMediaControl { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether session supports a persistent identifier. + /// </summary> + public bool SupportsPersistentIdentifier { get; set; } + + /// <summary> + /// Gets or sets the device profile. + /// </summary> + public DeviceProfile? DeviceProfile { get; set; } + + /// <summary> + /// Gets or sets the app store url. + /// </summary> + public string? AppStoreUrl { get; set; } + + /// <summary> + /// Gets or sets the icon url. + /// </summary> + public string? IconUrl { get; set; } + + /// <summary> + /// Convert the dto to the full <see cref="ClientCapabilities"/> model. + /// </summary> + /// <returns>The converted <see cref="ClientCapabilities"/> model.</returns> + public ClientCapabilities ToClientCapabilities() + { + return new ClientCapabilities + { + PlayableMediaTypes = PlayableMediaTypes, + SupportedCommands = SupportedCommands, + SupportsMediaControl = SupportsMediaControl, + SupportsPersistentIdentifier = SupportsPersistentIdentifier, + DeviceProfile = DeviceProfile, + AppStoreUrl = AppStoreUrl, + IconUrl = IconUrl + }; + } +} diff --git a/MediaBrowser.Model/Dto/DeviceInfoDto.cs b/MediaBrowser.Model/Dto/DeviceInfoDto.cs new file mode 100644 index 000000000..ac7a731a9 --- /dev/null +++ b/MediaBrowser.Model/Dto/DeviceInfoDto.cs @@ -0,0 +1,83 @@ +using System; + +namespace MediaBrowser.Model.Dto; + +/// <summary> +/// A DTO representing device information. +/// </summary> +public class DeviceInfoDto +{ + /// <summary> + /// Initializes a new instance of the <see cref="DeviceInfoDto"/> class. + /// </summary> + public DeviceInfoDto() + { + Capabilities = new ClientCapabilitiesDto(); + } + + /// <summary> + /// Gets or sets the name. + /// </summary> + /// <value>The name.</value> + public string? Name { get; set; } + + /// <summary> + /// Gets or sets the custom name. + /// </summary> + /// <value>The custom name.</value> + public string? CustomName { get; set; } + + /// <summary> + /// Gets or sets the access token. + /// </summary> + /// <value>The access token.</value> + public string? AccessToken { get; set; } + + /// <summary> + /// Gets or sets the identifier. + /// </summary> + /// <value>The identifier.</value> + public string? Id { get; set; } + + /// <summary> + /// Gets or sets the last name of the user. + /// </summary> + /// <value>The last name of the user.</value> + public string? LastUserName { get; set; } + + /// <summary> + /// Gets or sets the name of the application. + /// </summary> + /// <value>The name of the application.</value> + public string? AppName { get; set; } + + /// <summary> + /// Gets or sets the application version. + /// </summary> + /// <value>The application version.</value> + public string? AppVersion { get; set; } + + /// <summary> + /// Gets or sets the last user identifier. + /// </summary> + /// <value>The last user identifier.</value> + public Guid? LastUserId { get; set; } + + /// <summary> + /// Gets or sets the date last modified. + /// </summary> + /// <value>The date last modified.</value> + public DateTime? DateLastActivity { get; set; } + + /// <summary> + /// Gets or sets the capabilities. + /// </summary> + /// <value>The capabilities.</value> + public ClientCapabilitiesDto Capabilities { get; set; } + + /// <summary> + /// Gets or sets the icon URL. + /// </summary> + /// <value>The icon URL.</value> + public string? IconUrl { get; set; } +} diff --git a/MediaBrowser.Model/Dto/PlaylistDto.cs b/MediaBrowser.Model/Dto/PlaylistDto.cs new file mode 100644 index 000000000..d4de75a78 --- /dev/null +++ b/MediaBrowser.Model/Dto/PlaylistDto.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Model.Dto; + +/// <summary> +/// DTO for playlists. +/// </summary> +public class PlaylistDto +{ + /// <summary> + /// Gets or sets a value indicating whether the playlist is publicly readable. + /// </summary> + public bool OpenAccess { get; set; } + + /// <summary> + /// Gets or sets the share permissions. + /// </summary> + public required IReadOnlyList<PlaylistUserPermissions> Shares { get; set; } + + /// <summary> + /// Gets or sets the item ids. + /// </summary> + public required IReadOnlyList<Guid> ItemIds { get; set; } +} diff --git a/MediaBrowser.Model/Dto/SessionInfoDto.cs b/MediaBrowser.Model/Dto/SessionInfoDto.cs new file mode 100644 index 000000000..d727cd874 --- /dev/null +++ b/MediaBrowser.Model/Dto/SessionInfoDto.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using Jellyfin.Data.Enums; +using MediaBrowser.Model.Session; + +namespace MediaBrowser.Model.Dto; + +/// <summary> +/// Session info DTO. +/// </summary> +public class SessionInfoDto +{ + /// <summary> + /// Gets or sets the play state. + /// </summary> + /// <value>The play state.</value> + public PlayerStateInfo? PlayState { get; set; } + + /// <summary> + /// Gets or sets the additional users. + /// </summary> + /// <value>The additional users.</value> + public IReadOnlyList<SessionUserInfo>? AdditionalUsers { get; set; } + + /// <summary> + /// Gets or sets the client capabilities. + /// </summary> + /// <value>The client capabilities.</value> + public ClientCapabilitiesDto? Capabilities { get; set; } + + /// <summary> + /// Gets or sets the remote end point. + /// </summary> + /// <value>The remote end point.</value> + public string? RemoteEndPoint { get; set; } + + /// <summary> + /// Gets or sets the playable media types. + /// </summary> + /// <value>The playable media types.</value> + public IReadOnlyList<MediaType> PlayableMediaTypes { get; set; } = []; + + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + public string? Id { get; set; } + + /// <summary> + /// Gets or sets the user id. + /// </summary> + /// <value>The user id.</value> + public Guid UserId { get; set; } + + /// <summary> + /// Gets or sets the username. + /// </summary> + /// <value>The username.</value> + public string? UserName { get; set; } + + /// <summary> + /// Gets or sets the type of the client. + /// </summary> + /// <value>The type of the client.</value> + public string? Client { get; set; } + + /// <summary> + /// Gets or sets the last activity date. + /// </summary> + /// <value>The last activity date.</value> + public DateTime LastActivityDate { get; set; } + + /// <summary> + /// Gets or sets the last playback check in. + /// </summary> + /// <value>The last playback check in.</value> + public DateTime LastPlaybackCheckIn { get; set; } + + /// <summary> + /// Gets or sets the last paused date. + /// </summary> + /// <value>The last paused date.</value> + public DateTime? LastPausedDate { get; set; } + + /// <summary> + /// Gets or sets the name of the device. + /// </summary> + /// <value>The name of the device.</value> + public string? DeviceName { get; set; } + + /// <summary> + /// Gets or sets the type of the device. + /// </summary> + /// <value>The type of the device.</value> + public string? DeviceType { get; set; } + + /// <summary> + /// Gets or sets the now playing item. + /// </summary> + /// <value>The now playing item.</value> + public BaseItemDto? NowPlayingItem { get; set; } + + /// <summary> + /// Gets or sets the now viewing item. + /// </summary> + /// <value>The now viewing item.</value> + public BaseItemDto? NowViewingItem { get; set; } + + /// <summary> + /// Gets or sets the device id. + /// </summary> + /// <value>The device id.</value> + public string? DeviceId { get; set; } + + /// <summary> + /// Gets or sets the application version. + /// </summary> + /// <value>The application version.</value> + public string? ApplicationVersion { get; set; } + + /// <summary> + /// Gets or sets the transcoding info. + /// </summary> + /// <value>The transcoding info.</value> + public TranscodingInfo? TranscodingInfo { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this session is active. + /// </summary> + /// <value><c>true</c> if this session is active; otherwise, <c>false</c>.</value> + public bool IsActive { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the session supports media control. + /// </summary> + /// <value><c>true</c> if this session supports media control; otherwise, <c>false</c>.</value> + public bool SupportsMediaControl { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the session supports remote control. + /// </summary> + /// <value><c>true</c> if this session supports remote control; otherwise, <c>false</c>.</value> + public bool SupportsRemoteControl { get; set; } + + /// <summary> + /// Gets or sets the now playing queue. + /// </summary> + /// <value>The now playing queue.</value> + public IReadOnlyList<QueueItem>? NowPlayingQueue { get; set; } + + /// <summary> + /// Gets or sets the now playing queue full items. + /// </summary> + /// <value>The now playing queue full items.</value> + public IReadOnlyList<BaseItemDto>? NowPlayingQueueFullItems { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the session has a custom device name. + /// </summary> + /// <value><c>true</c> if this session has a custom device name; otherwise, <c>false</c>.</value> + public bool HasCustomDeviceName { get; set; } + + /// <summary> + /// Gets or sets the playlist item id. + /// </summary> + /// <value>The playlist item id.</value> + public string? PlaylistItemId { get; set; } + + /// <summary> + /// Gets or sets the server id. + /// </summary> + /// <value>The server id.</value> + public string? ServerId { get; set; } + + /// <summary> + /// Gets or sets the user primary image tag. + /// </summary> + /// <value>The user primary image tag.</value> + public string? UserPrimaryImageTag { get; set; } + + /// <summary> + /// Gets or sets the supported commands. + /// </summary> + /// <value>The supported commands.</value> + public IReadOnlyList<GeneralCommandType> SupportedCommands { get; set; } = []; +} 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..218a22aa2 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); } @@ -500,7 +500,7 @@ namespace MediaBrowser.Model.Entities /// Gets or sets a value indicating whether this instance is for the hearing impaired. /// </summary> /// <value><c>true</c> if this instance is for the hearing impaired; otherwise, <c>false</c>.</value> - public bool IsHearingImpaired { get; set; } + public bool? IsHearingImpaired { get; set; } /// <summary> /// Gets or sets the height. @@ -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..dcc4ae88c 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, 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/Extensions/LibraryOptionsExtension.cs b/MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs new file mode 100644 index 000000000..b088cfb53 --- /dev/null +++ b/MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using MediaBrowser.Model.Configuration; + +namespace MediaBrowser.Model.Extensions; + +/// <summary> +/// Extensions for <see cref="LibraryOptions"/>. +/// </summary> +public static class LibraryOptionsExtension +{ + /// <summary> + /// Get the custom tag delimiters. + /// </summary> + /// <param name="options">This LibraryOptions.</param> + /// <returns>CustomTagDelimiters in char[].</returns> + public static char[] GetCustomTagDelimiters(this LibraryOptions options) + { + ArgumentNullException.ThrowIfNull(options); + + var delimiterList = options.CustomTagDelimiters.Select<string, char?>(x => + { + var isChar = char.TryParse(x, out var c); + if (isChar) + { + return c; + } + + return null; + }).Where(x => x is not null).Select(x => x!.Value).ToList(); + delimiterList.Add('\0'); + return delimiterList.ToArray(); + } +} 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..229368d00 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,7 +153,7 @@ 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); diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 9489fe190..e9dab6bc8 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -8,13 +8,13 @@ <PropertyGroup> <Authors>Jellyfin Contributors</Authors> <PackageId>Jellyfin.Model</PackageId> - <VersionPrefix>10.10.0</VersionPrefix> + <VersionPrefix>10.11.0</VersionPrefix> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> </PropertyGroup> <PropertyGroup> - <TargetFramework>net8.0</TargetFramework> + <TargetFramework>net9.0</TargetFramework> <GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateDocumentationFile>true</GenerateDocumentationFile> <PublishRepositoryUrl>true</PublishRepositoryUrl> @@ -35,7 +35,7 @@ <ItemGroup> <FrameworkReference Include="Microsoft.AspNetCore.App" /> </ItemGroup> - + <ItemGroup> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" /> <PackageReference Include="MimeTypes"> diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs index 5d65b0f9b..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 @@ -125,6 +126,7 @@ namespace MediaBrowser.Model.Net new("audio/vorbis", ".vorbis"), new("audio/x-ape", ".ape"), new("audio/xsp", ".xsp"), + new("audio/x-aac", ".aac"), new("audio/x-wavpack", ".wv"), // Type image @@ -143,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/Playlists/PlaylistCreationRequest.cs b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs index ec54b1afd..98f7c6ce1 100644 --- a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs +++ b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs @@ -38,5 +38,5 @@ public class PlaylistCreationRequest /// <summary> /// Gets or sets a value indicating whether the playlist is public. /// </summary> - public bool? Public { get; set; } = true; + public bool? Public { get; set; } = false; } 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/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/Tasks/TaskTriggerInfo.cs b/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs index 63709557d..186c0aed3 100644 --- a/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs +++ b/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs @@ -9,30 +9,10 @@ namespace MediaBrowser.Model.Tasks public class TaskTriggerInfo { /// <summary> - /// The daily trigger. - /// </summary> - public const string TriggerDaily = "DailyTrigger"; - - /// <summary> - /// The weekly trigger. - /// </summary> - public const string TriggerWeekly = "WeeklyTrigger"; - - /// <summary> - /// The interval trigger. - /// </summary> - public const string TriggerInterval = "IntervalTrigger"; - - /// <summary> - /// The startup trigger. - /// </summary> - public const string TriggerStartup = "StartupTrigger"; - - /// <summary> /// Gets or sets the type. /// </summary> /// <value>The type.</value> - public string Type { get; set; } + public TaskTriggerInfoType Type { get; set; } /// <summary> /// Gets or sets the time of day. diff --git a/MediaBrowser.Model/Tasks/TaskTriggerInfoType.cs b/MediaBrowser.Model/Tasks/TaskTriggerInfoType.cs new file mode 100644 index 000000000..b596cf580 --- /dev/null +++ b/MediaBrowser.Model/Tasks/TaskTriggerInfoType.cs @@ -0,0 +1,28 @@ +namespace MediaBrowser.Model.Tasks +{ + /// <summary> + /// Enum TaskTriggerInfoType. + /// </summary> + public enum TaskTriggerInfoType + { + /// <summary> + /// The daily trigger. + /// </summary> + DailyTrigger, + + /// <summary> + /// The weekly trigger. + /// </summary> + WeeklyTrigger, + + /// <summary> + /// The interval trigger. + /// </summary> + IntervalTrigger, + + /// <summary> + /// The startup trigger. + /// </summary> + StartupTrigger + } +} |
