diff options
Diffstat (limited to 'MediaBrowser.Model')
59 files changed, 1127 insertions, 1098 deletions
diff --git a/MediaBrowser.Model/Branding/BrandingOptions.cs b/MediaBrowser.Model/Branding/BrandingOptions.cs index 695267d46..c6580598b 100644 --- a/MediaBrowser.Model/Branding/BrandingOptions.cs +++ b/MediaBrowser.Model/Branding/BrandingOptions.cs @@ -22,7 +22,7 @@ public class BrandingOptions /// <summary> /// Gets or sets a value indicating whether to enable the splashscreen. /// </summary> - public bool SplashscreenEnabled { get; set; } = true; + public bool SplashscreenEnabled { get; set; } = false; /// <summary> /// Gets or sets the splashscreen location on disk. diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 008050602..b5b6e86c8 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -1,134 +1,266 @@ #nullable disable -#pragma warning disable CS1591 +using MediaBrowser.Model.Entities; -namespace MediaBrowser.Model.Configuration +namespace MediaBrowser.Model.Configuration; + +/// <summary> +/// Class EncodingOptions. +/// </summary> +public class EncodingOptions { - public class EncodingOptions + /// <summary> + /// Initializes a new instance of the <see cref="EncodingOptions" /> class. + /// </summary> + public EncodingOptions() { - public EncodingOptions() - { - EnableFallbackFont = false; - DownMixAudioBoost = 2; - MaxMuxingQueueSize = 2048; - EnableThrottling = false; - EnableSegmentDeletion = false; - ThrottleDelaySeconds = 180; - SegmentKeepSeconds = 360; - EncodingThreadCount = -1; - // This is a DRM device that is almost guaranteed to be there on every intel platform, - // plus it's the default one in ffmpeg if you don't specify anything - VaapiDevice = "/dev/dri/renderD128"; - EnableTonemapping = false; - EnableVppTonemapping = false; - TonemappingAlgorithm = "bt2390"; - TonemappingRange = "auto"; - TonemappingDesat = 0; - TonemappingThreshold = 0.8; - TonemappingPeak = 100; - TonemappingParam = 0; - VppTonemappingBrightness = 0; - VppTonemappingContrast = 1.2; - H264Crf = 23; - H265Crf = 28; - DeinterlaceDoubleRate = false; - DeinterlaceMethod = "yadif"; - EnableDecodingColorDepth10Hevc = true; - EnableDecodingColorDepth10Vp9 = true; - EnableEnhancedNvdecDecoder = false; - PreferSystemNativeHwDecoder = true; - EnableIntelLowPowerH264HwEncoder = false; - EnableIntelLowPowerHevcHwEncoder = false; - EnableHardwareEncoding = true; - AllowHevcEncoding = false; - EnableSubtitleExtraction = true; - AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = new[] { "mkv" }; - HardwareDecodingCodecs = new string[] { "h264", "vc1" }; - } - - public int EncodingThreadCount { get; set; } - - public string TranscodingTempPath { get; set; } - - public string FallbackFontPath { get; set; } - - public bool EnableFallbackFont { get; set; } - - public double DownMixAudioBoost { get; set; } - - public int MaxMuxingQueueSize { get; set; } - - public bool EnableThrottling { get; set; } - - public bool EnableSegmentDeletion { get; set; } - - public int ThrottleDelaySeconds { get; set; } - - public int SegmentKeepSeconds { get; set; } - - public string HardwareAccelerationType { get; set; } - - /// <summary> - /// Gets or sets the FFmpeg path as set by the user via the UI. - /// </summary> - public string EncoderAppPath { get; set; } - - /// <summary> - /// Gets or sets the current FFmpeg path being used by the system and displayed on the transcode page. - /// </summary> - public string EncoderAppPathDisplay { get; set; } - - public string VaapiDevice { get; set; } - - public bool EnableTonemapping { get; set; } - - public bool EnableVppTonemapping { get; set; } - - public string TonemappingAlgorithm { get; set; } - - public string TonemappingRange { get; set; } - - public double TonemappingDesat { get; set; } - - public double TonemappingThreshold { get; set; } - - public double TonemappingPeak { get; set; } - - public double TonemappingParam { get; set; } - - public double VppTonemappingBrightness { get; set; } - - public double VppTonemappingContrast { get; set; } - - public int H264Crf { get; set; } - - public int H265Crf { get; set; } - - public string EncoderPreset { get; set; } - - public bool DeinterlaceDoubleRate { get; set; } - - public string DeinterlaceMethod { get; set; } - - public bool EnableDecodingColorDepth10Hevc { get; set; } - - public bool EnableDecodingColorDepth10Vp9 { get; set; } - - public bool EnableEnhancedNvdecDecoder { get; set; } - - public bool PreferSystemNativeHwDecoder { get; set; } - - public bool EnableIntelLowPowerH264HwEncoder { get; set; } - - public bool EnableIntelLowPowerHevcHwEncoder { get; set; } - - public bool EnableHardwareEncoding { get; set; } - - public bool AllowHevcEncoding { get; set; } - - public bool EnableSubtitleExtraction { get; set; } - - public string[] HardwareDecodingCodecs { get; set; } - - public string[] AllowOnDemandMetadataBasedKeyframeExtractionForExtensions { get; set; } + EnableFallbackFont = false; + EnableAudioVbr = false; + DownMixAudioBoost = 2; + DownMixStereoAlgorithm = DownMixStereoAlgorithms.None; + MaxMuxingQueueSize = 2048; + EnableThrottling = false; + ThrottleDelaySeconds = 180; + EnableSegmentDeletion = false; + SegmentKeepSeconds = 360; + EncodingThreadCount = -1; + // This is a DRM device that is almost guaranteed to be there on every intel platform, + // plus it's the default one in ffmpeg if you don't specify anything + VaapiDevice = "/dev/dri/renderD128"; + EnableTonemapping = false; + EnableVppTonemapping = false; + TonemappingAlgorithm = "bt2390"; + TonemappingMode = "auto"; + TonemappingRange = "auto"; + TonemappingDesat = 0; + TonemappingPeak = 100; + TonemappingParam = 0; + VppTonemappingBrightness = 16; + VppTonemappingContrast = 1; + H264Crf = 23; + H265Crf = 28; + DeinterlaceDoubleRate = false; + DeinterlaceMethod = "yadif"; + EnableDecodingColorDepth10Hevc = true; + EnableDecodingColorDepth10Vp9 = true; + // Enhanced Nvdec or system native decoder is required for DoVi to SDR tone-mapping. + EnableEnhancedNvdecDecoder = true; + PreferSystemNativeHwDecoder = true; + EnableIntelLowPowerH264HwEncoder = false; + EnableIntelLowPowerHevcHwEncoder = false; + EnableHardwareEncoding = true; + AllowHevcEncoding = false; + EnableSubtitleExtraction = true; + AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = new[] { "mkv" }; + HardwareDecodingCodecs = new string[] { "h264", "vc1" }; } + + /// <summary> + /// Gets or sets the thread count used for encoding. + /// </summary> + public int EncodingThreadCount { get; set; } + + /// <summary> + /// Gets or sets the temporary transcoding path. + /// </summary> + public string TranscodingTempPath { get; set; } + + /// <summary> + /// Gets or sets the path to the fallback font. + /// </summary> + public string FallbackFontPath { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to use the fallback font. + /// </summary> + public bool EnableFallbackFont { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether audio VBR is enabled. + /// </summary> + public bool EnableAudioVbr { get; set; } + + /// <summary> + /// Gets or sets the audio boost applied when downmixing audio. + /// </summary> + public double DownMixAudioBoost { get; set; } + + /// <summary> + /// Gets or sets the algorithm used for downmixing audio to stereo. + /// </summary> + public DownMixStereoAlgorithms DownMixStereoAlgorithm { get; set; } + + /// <summary> + /// Gets or sets the maximum size of the muxing queue. + /// </summary> + public int MaxMuxingQueueSize { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether throttling is enabled. + /// </summary> + public bool EnableThrottling { get; set; } + + /// <summary> + /// Gets or sets the delay after which throttling happens. + /// </summary> + public int ThrottleDelaySeconds { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether segment deletion is enabled. + /// </summary> + public bool EnableSegmentDeletion { get; set; } + + /// <summary> + /// Gets or sets seconds for which segments should be kept before being deleted. + /// </summary> + public int SegmentKeepSeconds { get; set; } + + /// <summary> + /// Gets or sets the hardware acceleration type. + /// </summary> + public string HardwareAccelerationType { get; set; } + + /// <summary> + /// Gets or sets the FFmpeg path as set by the user via the UI. + /// </summary> + public string EncoderAppPath { get; set; } + + /// <summary> + /// Gets or sets the current FFmpeg path being used by the system and displayed on the transcode page. + /// </summary> + public string EncoderAppPathDisplay { get; set; } + + /// <summary> + /// Gets or sets the VA-API device. + /// </summary> + public string VaapiDevice { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether tonemapping is enabled. + /// </summary> + public bool EnableTonemapping { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether VPP tonemapping is enabled. + /// </summary> + public bool EnableVppTonemapping { get; set; } + + /// <summary> + /// Gets or sets the tone-mapping algorithm. + /// </summary> + public string TonemappingAlgorithm { get; set; } + + /// <summary> + /// Gets or sets the tone-mapping mode. + /// </summary> + public string TonemappingMode { get; set; } + + /// <summary> + /// Gets or sets the tone-mapping range. + /// </summary> + public string TonemappingRange { get; set; } + + /// <summary> + /// Gets or sets the tone-mapping desaturation. + /// </summary> + public double TonemappingDesat { get; set; } + + /// <summary> + /// Gets or sets the tone-mapping peak. + /// </summary> + public double TonemappingPeak { get; set; } + + /// <summary> + /// Gets or sets the tone-mapping parameters. + /// </summary> + public double TonemappingParam { get; set; } + + /// <summary> + /// Gets or sets the VPP tone-mapping brightness. + /// </summary> + public double VppTonemappingBrightness { get; set; } + + /// <summary> + /// Gets or sets the VPP tone-mapping contrast. + /// </summary> + public double VppTonemappingContrast { get; set; } + + /// <summary> + /// Gets or sets the H264 CRF. + /// </summary> + public int H264Crf { get; set; } + + /// <summary> + /// Gets or sets the H265 CRF. + /// </summary> + public int H265Crf { get; set; } + + /// <summary> + /// Gets or sets the encoder preset. + /// </summary> + public string EncoderPreset { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the framerate is doubled when deinterlacing. + /// </summary> + public bool DeinterlaceDoubleRate { get; set; } + + /// <summary> + /// Gets or sets the deinterlace method. + /// </summary> + public string DeinterlaceMethod { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether 10bit HEVC decoding is enabled. + /// </summary> + public bool EnableDecodingColorDepth10Hevc { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether 10bit VP9 decoding is enabled. + /// </summary> + public bool EnableDecodingColorDepth10Vp9 { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the enhanced NVDEC is enabled. + /// </summary> + public bool EnableEnhancedNvdecDecoder { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the system native hardware decoder should be used. + /// </summary> + public bool PreferSystemNativeHwDecoder { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the Intel H264 low-power hardware encoder should be used. + /// </summary> + public bool EnableIntelLowPowerH264HwEncoder { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the Intel HEVC low-power hardware encoder should be used. + /// </summary> + public bool EnableIntelLowPowerHevcHwEncoder { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether hardware encoding is enabled. + /// </summary> + public bool EnableHardwareEncoding { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether HEVC encoding is enabled. + /// </summary> + public bool AllowHevcEncoding { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether subtitle extraction is enabled. + /// </summary> + public bool EnableSubtitleExtraction { get; set; } + + /// <summary> + /// Gets or sets the codecs hardware encoding is used for. + /// </summary> + public string[] HardwareDecodingCodecs { get; set; } + + /// <summary> + /// Gets or sets the file extensions on-demand metadata based keyframe extraction is enabled for. + /// </summary> + public string[] AllowOnDemandMetadataBasedKeyframeExtractionForExtensions { get; set; } } diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index c4d313bdb..df6829946 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -30,6 +30,8 @@ namespace MediaBrowser.Model.Configuration public bool EnableRealtimeMonitor { get; set; } + public bool EnableLUFSScan { get; set; } + public bool EnableChapterImageExtraction { get; set; } public bool ExtractChapterImagesDuringLibraryScan { get; set; } @@ -45,6 +47,8 @@ namespace MediaBrowser.Model.Configuration public bool EnableEmbeddedTitles { get; set; } + public bool EnableEmbeddedExtrasTitles { get; set; } + public bool EnableEmbeddedEpisodeInfos { get; set; } public int AutomaticRefreshIntervalDays { get; set; } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index e61b896b9..07f02d187 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -2,7 +2,7 @@ #pragma warning disable CA1819 using System; -using System.Collections.Generic; +using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Updates; @@ -193,7 +193,7 @@ namespace MediaBrowser.Model.Configuration public string[] CodecsUsed { get; set; } = Array.Empty<string>(); - public List<RepositoryInfo> PluginRepositories { get; set; } = new List<RepositoryInfo>(); + public RepositoryInfo[] PluginRepositories { get; set; } = Array.Empty<RepositoryInfo>(); public bool EnableExternalContentInSuggestions { get; set; } = true; @@ -240,5 +240,23 @@ namespace MediaBrowser.Model.Configuration /// 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. + /// </summary> + /// <value>The dummy chapters duration.</value> + public int DummyChapterDuration { get; set; } = 0; + + /// <summary> + /// Gets or sets the chapter image resolution. + /// </summary> + /// <value>The chapter image resolution.</value> + public ImageResolution ChapterImageResolution { get; set; } = ImageResolution.MatchSource; + + /// <summary> + /// Gets or sets the limit for parallel image encoding. + /// </summary> + /// <value>The limit for parallel image encoding.</value> + public int ParallelImageEncodingLimit { get; set; } = 0; } } diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index 32a34d23c..ccb361c13 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -29,12 +29,7 @@ namespace MediaBrowser.Model.Cryptography public PasswordHash(string id, byte[] hash, byte[] salt, Dictionary<string, string> parameters) { - ArgumentNullException.ThrowIfNull(id); - - if (id.Length == 0) - { - throw new ArgumentException("String can't be empty", nameof(id)); - } + ArgumentException.ThrowIfNullOrEmpty(id); Id = id; _hash = hash; @@ -85,7 +80,8 @@ namespace MediaBrowser.Model.Cryptography { throw new FormatException("Hash string must contain a valid id"); } - else if (nextSegment == -1) + + if (nextSegment == -1) { return new PasswordHash(hashString.ToString(), Array.Empty<byte>()); } diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs index 573422416..f5e1a3c49 100644 --- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs +++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs @@ -136,12 +136,26 @@ namespace MediaBrowser.Model.Dlna return !condition.IsRequired; } - if (int.TryParse(condition.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var expected)) + var conditionType = condition.Condition; + if (condition.Condition == ProfileConditionType.EqualsAny) { - switch (condition.Condition) + foreach (var singleConditionString in condition.Value.AsSpan().Split('|')) + { + if (int.TryParse(singleConditionString, NumberStyles.Integer, CultureInfo.InvariantCulture, out int conditionValue) + && conditionValue.Equals(currentValue)) + { + return true; + } + } + + return false; + } + + if (int.TryParse(condition.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var expected)) + { + switch (conditionType) { case ProfileConditionType.Equals: - case ProfileConditionType.EqualsAny: return currentValue.Value.Equals(expected); case ProfileConditionType.GreaterThanEqual: return currentValue.Value >= expected; @@ -212,9 +226,24 @@ namespace MediaBrowser.Model.Dlna return !condition.IsRequired; } - if (double.TryParse(condition.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var expected)) + var conditionType = condition.Condition; + if (condition.Condition == ProfileConditionType.EqualsAny) { - switch (condition.Condition) + foreach (var singleConditionString in condition.Value.AsSpan().Split('|')) + { + if (double.TryParse(singleConditionString, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out double conditionValue) + && conditionValue.Equals(currentValue)) + { + return true; + } + } + + return false; + } + + if (double.TryParse(condition.Value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var expected)) + { + switch (conditionType) { case ProfileConditionType.Equals: return currentValue.Value.Equals(expected); diff --git a/MediaBrowser.Model/Dlna/ContainerProfile.cs b/MediaBrowser.Model/Dlna/ContainerProfile.cs index c6befdd85..978004268 100644 --- a/MediaBrowser.Model/Dlna/ContainerProfile.cs +++ b/MediaBrowser.Model/Dlna/ContainerProfile.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Model.Dlna [XmlAttribute("type")] public DlnaProfileType Type { get; set; } - public ProfileCondition[]? Conditions { get; set; } = Array.Empty<ProfileCondition>(); + public ProfileCondition[] Conditions { get; set; } = Array.Empty<ProfileCondition>(); [XmlAttribute("container")] public string Container { get; set; } = string.Empty; @@ -36,7 +36,7 @@ namespace MediaBrowser.Model.Dlna public static bool ContainsContainer(string? profileContainers, string? inputContainer) { var isNegativeList = false; - if (profileContainers != null && profileContainers.StartsWith('-')) + if (profileContainers is not null && profileContainers.StartsWith('-')) { isNegativeList = true; profileContainers = profileContainers.Substring(1); @@ -52,7 +52,7 @@ namespace MediaBrowser.Model.Dlna public static bool ContainsContainer(string[]? profileContainers, bool isNegativeList, string? inputContainer) { - if (profileContainers == null || profileContainers.Length == 0) + if (profileContainers is null || profileContainers.Length == 0) { // Empty profiles always support all containers/codecs return true; diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs index 1a9576361..1d5d0b1de 100644 --- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs +++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs @@ -192,7 +192,7 @@ namespace MediaBrowser.Model.Dlna var orgPnValues = new List<string>(); - if (mediaProfile != null && !string.IsNullOrEmpty(mediaProfile.OrgPn)) + if (mediaProfile is not null && !string.IsNullOrEmpty(mediaProfile.OrgPn)) { orgPnValues.AddRange(mediaProfile.OrgPn.Split(',', StringSplitOptions.RemoveEmptyEntries)); } diff --git a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs index 03c3a7265..f68235d86 100644 --- a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs +++ b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs @@ -18,17 +18,17 @@ namespace MediaBrowser.Model.Dlna [XmlAttribute("type")] public DlnaProfileType Type { get; set; } - public bool SupportsContainer(string container) + public bool SupportsContainer(string? container) { return ContainerProfile.ContainsContainer(Container, container); } - public bool SupportsVideoCodec(string codec) + public bool SupportsVideoCodec(string? codec) { return Type == DlnaProfileType.Video && ContainerProfile.ContainsContainer(VideoCodec, codec); } - public bool SupportsAudioCodec(string codec) + public bool SupportsAudioCodec(string? codec) { return (Type == DlnaProfileType.Audio || Type == DlnaProfileType.Video) && ContainerProfile.ContainsContainer(AudioCodec, codec); } diff --git a/MediaBrowser.Model/Dlna/ITranscoderSupport.cs b/MediaBrowser.Model/Dlna/ITranscoderSupport.cs index a70ce44cc..d7397399d 100644 --- a/MediaBrowser.Model/Dlna/ITranscoderSupport.cs +++ b/MediaBrowser.Model/Dlna/ITranscoderSupport.cs @@ -10,22 +10,4 @@ namespace MediaBrowser.Model.Dlna bool CanExtractSubtitles(string codec); } - - public class FullTranscoderSupport : ITranscoderSupport - { - public bool CanEncodeToAudioCodec(string codec) - { - return true; - } - - public bool CanEncodeToSubtitleCodec(string codec) - { - return true; - } - - public bool CanExtractSubtitles(string codec) - { - return true; - } - } } diff --git a/MediaBrowser.Model/Dlna/AudioOptions.cs b/MediaBrowser.Model/Dlna/MediaOptions.cs index 33755e746..7ec0dd473 100644 --- a/MediaBrowser.Model/Dlna/AudioOptions.cs +++ b/MediaBrowser.Model/Dlna/MediaOptions.cs @@ -1,17 +1,17 @@ -#nullable disable -#pragma warning disable CS1591 - using System; using MediaBrowser.Model.Dto; namespace MediaBrowser.Model.Dlna { /// <summary> - /// Class AudioOptions. + /// Class MediaOptions. /// </summary> - public class AudioOptions + public class MediaOptions { - public AudioOptions() + /// <summary> + /// Initializes a new instance of the <see cref="MediaOptions"/> class. + /// </summary> + public MediaOptions() { Context = EncodingContext.Streaming; @@ -19,28 +19,60 @@ namespace MediaBrowser.Model.Dlna EnableDirectStream = true; } + /// <summary> + /// Gets or sets a value indicating whether direct playback is allowed. + /// </summary> public bool EnableDirectPlay { get; set; } + /// <summary> + /// Gets or sets a value indicating whether direct streaming is allowed. + /// </summary> public bool EnableDirectStream { get; set; } + /// <summary> + /// Gets or sets a value indicating whether direct playback is forced. + /// </summary> public bool ForceDirectPlay { get; set; } + /// <summary> + /// Gets or sets a value indicating whether direct streaming is forced. + /// </summary> public bool ForceDirectStream { get; set; } + /// <summary> + /// Gets or sets a value indicating whether audio stream copy is allowed. + /// </summary> public bool AllowAudioStreamCopy { get; set; } + /// <summary> + /// Gets or sets a value indicating whether video stream copy is allowed. + /// </summary> + public bool AllowVideoStreamCopy { get; set; } + + /// <summary> + /// Gets or sets the item id. + /// </summary> public Guid ItemId { get; set; } - public MediaSourceInfo[] MediaSources { get; set; } + /// <summary> + /// Gets or sets the media sources. + /// </summary> + public MediaSourceInfo[] MediaSources { get; set; } = Array.Empty<MediaSourceInfo>(); - public DeviceProfile Profile { get; set; } + /// <summary> + /// Gets or sets the device profile. + /// </summary> + required public DeviceProfile Profile { get; set; } /// <summary> /// Gets or sets a media source id. Optional. Only needed if a specific AudioStreamIndex or SubtitleStreamIndex are requested. /// </summary> - public string MediaSourceId { get; set; } + public string? MediaSourceId { get; set; } - public string DeviceId { get; set; } + /// <summary> + /// Gets or sets the device id. + /// </summary> + public string? DeviceId { get; set; } /// <summary> /// Gets or sets an override of supported number of audio channels @@ -49,7 +81,7 @@ namespace MediaBrowser.Model.Dlna public int? MaxAudioChannels { get; set; } /// <summary> - /// Gets or sets the application's configured quality setting. + /// Gets or sets the application's configured maximum bitrate. /// </summary> public int? MaxBitrate { get; set; } @@ -66,6 +98,16 @@ namespace MediaBrowser.Model.Dlna public int? AudioTranscodingBitrate { get; set; } /// <summary> + /// Gets or sets an override for the audio stream index. + /// </summary> + public int? AudioStreamIndex { get; set; } + + /// <summary> + /// Gets or sets an override for the subtitle stream index. + /// </summary> + public int? SubtitleStreamIndex { get; set; } + + /// <summary> /// Gets the maximum bitrate. /// </summary> /// <param name="isAudio">Whether or not this is audio.</param> @@ -77,7 +119,7 @@ namespace MediaBrowser.Model.Dlna return MaxBitrate; } - if (Profile == null) + if (Profile is null) { return null; } diff --git a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs index 94071b419..ce422a228 100644 --- a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs +++ b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs @@ -39,7 +39,7 @@ namespace MediaBrowser.Model.Dlna } var resolutionConfig = GetResolutionConfiguration(outputBitrate); - if (resolutionConfig != null) + if (resolutionConfig is not null) { var originvalValue = maxWidth; diff --git a/MediaBrowser.Model/Dlna/SearchCriteria.cs b/MediaBrowser.Model/Dlna/SearchCriteria.cs index b1fc48c08..77d6a55ea 100644 --- a/MediaBrowser.Model/Dlna/SearchCriteria.cs +++ b/MediaBrowser.Model/Dlna/SearchCriteria.cs @@ -9,10 +9,7 @@ namespace MediaBrowser.Model.Dlna { public SearchCriteria(string search) { - if (search.Length == 0) - { - throw new ArgumentException("String can't be empty.", nameof(search)); - } + ArgumentException.ThrowIfNullOrEmpty(search); SearchType = SearchType.Unknown; diff --git a/MediaBrowser.Model/Dlna/SortCriteria.cs b/MediaBrowser.Model/Dlna/SortCriteria.cs index 7fef16e53..7df53c6d1 100644 --- a/MediaBrowser.Model/Dlna/SortCriteria.cs +++ b/MediaBrowser.Model/Dlna/SortCriteria.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Model.Dlna { public SortCriteria(string sortOrder) { - if (!string.IsNullOrEmpty(sortOrder) && Enum.TryParse<SortOrder>(sortOrder, true, out var sortOrderValue)) + if (Enum.TryParse<SortOrder>(sortOrder, true, out var sortOrderValue)) { SortOrder = sortOrderValue; } diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 6e9b943f7..0a955e917 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -1,6 +1,3 @@ -#nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -13,6 +10,9 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Model.Dlna { + /// <summary> + /// Class StreamBuilder. + /// </summary> public class StreamBuilder { // Aliases @@ -23,76 +23,182 @@ namespace MediaBrowser.Model.Dlna private readonly ILogger _logger; private readonly ITranscoderSupport _transcoderSupport; - + private static readonly string[] _supportedHlsVideoCodecs = new string[] { "h264", "hevc" }; + private static readonly string[] _supportedHlsAudioCodecsTs = new string[] { "aac", "ac3", "eac3", "mp3" }; + private static readonly string[] _supportedHlsAudioCodecsMp4 = new string[] { "aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd" }; + + /// <summary> + /// Initializes a new instance of the <see cref="StreamBuilder"/> class. + /// </summary> + /// <param name="transcoderSupport">The <see cref="ITranscoderSupport"/> object.</param> + /// <param name="logger">The <see cref="ILogger"/> object.</param> public StreamBuilder(ITranscoderSupport transcoderSupport, ILogger logger) { _transcoderSupport = transcoderSupport; _logger = logger; } - public StreamBuilder(ILogger<StreamBuilder> logger) - : this(new FullTranscoderSupport(), logger) + /// <summary> + /// Gets the optimal audio stream. + /// </summary> + /// <param name="options">The <see cref="MediaOptions"/> object to get the audio stream from.</param> + /// <returns>The <see cref="StreamInfo"/> of the optimal audio stream.</returns> + public StreamInfo? GetOptimalAudioStream(MediaOptions options) { + ValidateMediaOptions(options, false); + + var streams = new List<StreamInfo>(); + foreach (var mediaSource in options.MediaSources) + { + if (!(string.IsNullOrEmpty(options.MediaSourceId) + || string.Equals(mediaSource.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase))) + { + continue; + } + + StreamInfo? streamInfo = GetOptimalAudioStream(mediaSource, options); + if (streamInfo is not null) + { + streamInfo.DeviceId = options.DeviceId; + streamInfo.DeviceProfileId = options.Profile.Id; + streams.Add(streamInfo); + } + } + + return GetOptimalStream(streams, options.GetMaxBitrate(true) ?? 0); } - public StreamInfo BuildAudioItem(AudioOptions options) + private StreamInfo? GetOptimalAudioStream(MediaSourceInfo item, MediaOptions options) { - ValidateAudioInput(options); + var playlistItem = new StreamInfo + { + ItemId = options.ItemId, + MediaType = DlnaProfileType.Audio, + MediaSource = item, + RunTimeTicks = item.RunTimeTicks, + Context = options.Context, + DeviceProfile = options.Profile + }; - var mediaSources = new List<MediaSourceInfo>(); - foreach (MediaSourceInfo i in options.MediaSources) + if (options.ForceDirectPlay) + { + playlistItem.PlayMethod = PlayMethod.DirectPlay; + playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio); + return playlistItem; + } + + if (options.ForceDirectStream) { - if (string.IsNullOrEmpty(options.MediaSourceId) || - string.Equals(i.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase)) + playlistItem.PlayMethod = PlayMethod.DirectStream; + playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio); + return playlistItem; + } + + MediaStream audioStream = item.GetDefaultAudioStream(null); + + 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.HasValue) + { + 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); + transcodeReasons |= audioFailureReasons; + + if (audioFailureReasons == 0) { - mediaSources.Add(i); + playlistItem.PlayMethod = directPlayMethod.Value; + playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio, directPlayInfo.Profile); + + return playlistItem; } } - var streams = new List<StreamInfo>(); - foreach (MediaSourceInfo i in mediaSources) + TranscodingProfile? transcodingProfile = null; + foreach (var tcProfile in options.Profile.TranscodingProfiles) { - StreamInfo streamInfo = BuildAudioItem(i, options); - if (streamInfo != null) + if (tcProfile.Type == playlistItem.MediaType + && tcProfile.Context == options.Context + && _transcoderSupport.CanEncodeToAudioCodec(tcProfile.AudioCodec ?? tcProfile.Container)) { - streams.Add(streamInfo); + transcodingProfile = tcProfile; + break; } } - foreach (StreamInfo stream in streams) + if (transcodingProfile != null) { - stream.DeviceId = options.DeviceId; - stream.DeviceProfileId = options.Profile.Id; + if (!item.SupportsTranscoding) + { + return null; + } + + SetStreamInfoOptionsFromTranscodingProfile(item, playlistItem, transcodingProfile); + + var audioTranscodingConditions = GetProfileConditionsForAudio(options.Profile.CodecProfiles, transcodingProfile.Container, transcodingProfile.AudioCodec, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, false).ToArray(); + ApplyTranscodingConditions(playlistItem, audioTranscodingConditions, null, true, true); + + // Honor requested max channels + playlistItem.GlobalMaxAudioChannels = options.MaxAudioChannels; + + var configuredBitrate = options.GetMaxBitrate(true); + + long transcodingBitrate = options.AudioTranscodingBitrate + ?? (options.Context == EncodingContext.Streaming ? options.Profile.MusicStreamingTranscodingBitrate : null) + ?? configuredBitrate + ?? 128000; + + if (configuredBitrate.HasValue) + { + transcodingBitrate = Math.Min(configuredBitrate.Value, transcodingBitrate); + } + + var longBitrate = Math.Min(transcodingBitrate, playlistItem.AudioBitrate ?? transcodingBitrate); + playlistItem.AudioBitrate = longBitrate > int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate); } - return GetOptimalStream(streams, options.GetMaxBitrate(true) ?? 0); + playlistItem.TranscodeReasons = transcodeReasons; + return playlistItem; } - public StreamInfo BuildVideoItem(VideoOptions options) + /// <summary> + /// Gets the optimal video stream. + /// </summary> + /// <param name="options">The <see cref="MediaOptions"/> object to get the video stream from.</param> + /// <returns>The <see cref="StreamInfo"/> of the optimal video stream.</returns> + public StreamInfo? GetOptimalVideoStream(MediaOptions options) { - ValidateInput(options); + ValidateMediaOptions(options, true); var mediaSources = new List<MediaSourceInfo>(); - foreach (MediaSourceInfo i in options.MediaSources) + foreach (var mediaSourceInfo in options.MediaSources) { - if (string.IsNullOrEmpty(options.MediaSourceId) || - string.Equals(i.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase)) + if (string.IsNullOrEmpty(options.MediaSourceId) + || string.Equals(mediaSourceInfo.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase)) { - mediaSources.Add(i); + mediaSources.Add(mediaSourceInfo); } } var streams = new List<StreamInfo>(); - foreach (MediaSourceInfo i in mediaSources) + foreach (var mediaSourceInfo in mediaSources) { - var streamInfo = BuildVideoItem(i, options); - if (streamInfo != null) + var streamInfo = BuildVideoItem(mediaSourceInfo, options); + if (streamInfo is not null) { streams.Add(streamInfo); } } - foreach (StreamInfo stream in streams) + foreach (var stream in streams) { stream.DeviceId = options.DeviceId; stream.DeviceProfileId = options.Profile.Id; @@ -101,7 +207,7 @@ namespace MediaBrowser.Model.Dlna return GetOptimalStream(streams, options.GetMaxBitrate(false) ?? 0); } - private static StreamInfo GetOptimalStream(List<StreamInfo> streams, long maxBitrate) + private static StreamInfo? GetOptimalStream(List<StreamInfo> streams, long maxBitrate) => SortMediaSources(streams, maxBitrate).FirstOrDefault(); private static IOrderedEnumerable<StreamInfo> SortMediaSources(List<StreamInfo> streams, long maxBitrate) @@ -236,7 +342,15 @@ namespace MediaBrowser.Model.Dlna } } - public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile profile, DlnaProfileType type, DirectPlayProfile playProfile = null) + /// <summary> + /// Normalizes input container. + /// </summary> + /// <param name="inputContainer">The input container.</param> + /// <param name="profile">The <see cref="DeviceProfile"/>.</param> + /// <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 the normalized input container.</returns> + public static string? NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile? profile, DlnaProfileType type, DirectPlayProfile? playProfile = null) { if (string.IsNullOrEmpty(inputContainer)) { @@ -245,9 +359,9 @@ namespace MediaBrowser.Model.Dlna var formats = ContainerProfile.SplitValue(inputContainer); - if (profile != null) + if (profile is not null) { - var playProfiles = playProfile == null ? profile.DirectPlayProfiles : new[] { playProfile }; + var playProfiles = playProfile is null ? profile.DirectPlayProfiles : new[] { playProfile }; foreach (var format in formats) { foreach (var directPlayProfile in playProfiles) @@ -264,113 +378,12 @@ namespace MediaBrowser.Model.Dlna return formats[0]; } - private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options) - { - StreamInfo playlistItem = new StreamInfo - { - ItemId = options.ItemId, - MediaType = DlnaProfileType.Audio, - MediaSource = item, - RunTimeTicks = item.RunTimeTicks, - Context = options.Context, - DeviceProfile = options.Profile - }; - - if (options.ForceDirectPlay) - { - playlistItem.PlayMethod = PlayMethod.DirectPlay; - playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio); - return playlistItem; - } - - if (options.ForceDirectStream) - { - playlistItem.PlayMethod = PlayMethod.DirectStream; - playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio); - return playlistItem; - } - - var audioStream = item.GetDefaultAudioStream(null); - - var directPlayInfo = GetAudioDirectPlayProfile(item, audioStream, options); - - var directPlayMethod = directPlayInfo.PlayMethod; - var transcodeReasons = directPlayInfo.TranscodeReasons; - - int? inputAudioChannels = audioStream?.Channels; - int? inputAudioBitrate = audioStream?.BitDepth; - int? inputAudioSampleRate = audioStream?.SampleRate; - int? inputAudioBitDepth = audioStream?.BitDepth; - - if (directPlayMethod.HasValue) - { - 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); - transcodeReasons |= audioFailureReasons; - - if (audioFailureReasons == 0) - { - playlistItem.PlayMethod = directPlayMethod.Value; - playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio, directPlayInfo.Profile); - - return playlistItem; - } - } - - TranscodingProfile transcodingProfile = null; - foreach (var i in options.Profile.TranscodingProfiles) - { - if (i.Type == playlistItem.MediaType - && i.Context == options.Context - && _transcoderSupport.CanEncodeToAudioCodec(i.AudioCodec ?? i.Container)) - { - transcodingProfile = i; - break; - } - } - - if (transcodingProfile != null) - { - if (!item.SupportsTranscoding) - { - return null; - } - - SetStreamInfoOptionsFromTranscodingProfile(item, playlistItem, transcodingProfile); - - var audioTranscodingConditions = GetProfileConditionsForAudio(options.Profile.CodecProfiles, transcodingProfile.Container, transcodingProfile.AudioCodec, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, false).ToArray(); - ApplyTranscodingConditions(playlistItem, audioTranscodingConditions, null, true, true); - - // Honor requested max channels - playlistItem.GlobalMaxAudioChannels = options.MaxAudioChannels; - - var configuredBitrate = options.GetMaxBitrate(true); - - long transcodingBitrate = options.AudioTranscodingBitrate ?? - (options.Context == EncodingContext.Streaming ? options.Profile.MusicStreamingTranscodingBitrate : null) ?? - configuredBitrate ?? - 128000; - - if (configuredBitrate.HasValue) - { - transcodingBitrate = Math.Min(configuredBitrate.Value, transcodingBitrate); - } - - var longBitrate = Math.Min(transcodingBitrate, playlistItem.AudioBitrate ?? transcodingBitrate); - playlistItem.AudioBitrate = longBitrate > int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate); - } - - playlistItem.TranscodeReasons = transcodeReasons; - return playlistItem; - } - - private (DirectPlayProfile Profile, PlayMethod? PlayMethod, TranscodeReason TranscodeReasons) GetAudioDirectPlayProfile(MediaSourceInfo item, MediaStream audioStream, AudioOptions options) + private (DirectPlayProfile? Profile, PlayMethod? PlayMethod, TranscodeReason TranscodeReasons) GetAudioDirectPlayProfile(MediaSourceInfo item, MediaStream audioStream, MediaOptions options) { var directPlayProfile = options.Profile.DirectPlayProfiles .FirstOrDefault(x => x.Type == DlnaProfileType.Audio && IsAudioDirectPlaySupported(x, item, audioStream)); - if (directPlayProfile == null) + if (directPlayProfile is null) { _logger.LogDebug( "Profile: {0}, No audio direct play profiles found for {1} with codec {2}", @@ -381,14 +394,13 @@ namespace MediaBrowser.Model.Dlna return (null, null, GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profile.DirectPlayProfiles)); } - var playMethods = new List<PlayMethod>(); TranscodeReason transcodeReasons = 0; // The profile describes what the device supports // If device requirements are satisfied then allow both direct stream and direct play if (item.SupportsDirectPlay) { - if (IsItemBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectPlay)) + if (!IsBitrateLimitExceeded(item, options.GetMaxBitrate(true) ?? 0)) { if (options.EnableDirectPlay) { @@ -404,7 +416,7 @@ namespace MediaBrowser.Model.Dlna // While options takes the network and other factors into account. Only applies to direct stream if (item.SupportsDirectStream) { - if (IsItemBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream)) + if (!IsBitrateLimitExceeded(item, options.GetMaxBitrate(true) ?? 0)) { if (options.EnableDirectStream) { @@ -420,14 +432,13 @@ namespace MediaBrowser.Model.Dlna return (directPlayProfile, null, transcodeReasons); } - private static TranscodeReason GetTranscodeReasonsFromDirectPlayProfile(MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable<DirectPlayProfile> directPlayProfiles) + private static TranscodeReason GetTranscodeReasonsFromDirectPlayProfile(MediaSourceInfo item, MediaStream? videoStream, MediaStream audioStream, IEnumerable<DirectPlayProfile> directPlayProfiles) { - var mediaType = videoStream == null ? DlnaProfileType.Audio : DlnaProfileType.Video; + var mediaType = videoStream is null ? DlnaProfileType.Audio : DlnaProfileType.Video; var containerSupported = false; var audioSupported = false; var videoSupported = false; - TranscodeReason reasons = 0; foreach (var profile in directPlayProfiles) { @@ -436,9 +447,9 @@ namespace MediaBrowser.Model.Dlna { containerSupported = true; - videoSupported = videoStream == null || profile.SupportsVideoCodec(videoStream.Codec); + videoSupported = videoStream is null || profile.SupportsVideoCodec(videoStream.Codec); - audioSupported = audioStream == null || profile.SupportsAudioCodec(audioStream.Codec); + audioSupported = audioStream is null || profile.SupportsAudioCodec(audioStream.Codec); if (videoSupported && audioSupported) { @@ -447,6 +458,7 @@ namespace MediaBrowser.Model.Dlna } } + TranscodeReason reasons = 0; if (!containerSupported) { reasons |= TranscodeReason.ContainerNotSupported; @@ -522,8 +534,7 @@ namespace MediaBrowser.Model.Dlna } playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; - if (!string.IsNullOrEmpty(transcodingProfile.MaxAudioChannels) - && int.TryParse(transcodingProfile.MaxAudioChannels, NumberStyles.Any, CultureInfo.InvariantCulture, out int transcodingMaxAudioChannels)) + if (int.TryParse(transcodingProfile.MaxAudioChannels, CultureInfo.InvariantCulture, out int transcodingMaxAudioChannels)) { playlistItem.TranscodingMaxAudioChannels = transcodingMaxAudioChannels; } @@ -547,7 +558,7 @@ namespace MediaBrowser.Model.Dlna } } - private static void SetStreamInfoOptionsFromDirectPlayProfile(VideoOptions options, MediaSourceInfo item, StreamInfo playlistItem, DirectPlayProfile directPlayProfile) + private static void SetStreamInfoOptionsFromDirectPlayProfile(MediaOptions options, MediaSourceInfo item, StreamInfo playlistItem, DirectPlayProfile? directPlayProfile) { var container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video, directPlayProfile); var protocol = "http"; @@ -559,10 +570,10 @@ namespace MediaBrowser.Model.Dlna playlistItem.SubProtocol = protocol; playlistItem.VideoCodecs = new[] { item.VideoStream.Codec }; - playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile.AudioCodec); + playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile?.AudioCodec); } - private StreamInfo BuildVideoItem(MediaSourceInfo item, VideoOptions options) + private StreamInfo BuildVideoItem(MediaSourceInfo item, MediaOptions options) { ArgumentNullException.ThrowIfNull(item); @@ -580,13 +591,13 @@ namespace MediaBrowser.Model.Dlna var subtitleStream = playlistItem.SubtitleStreamIndex.HasValue ? item.GetMediaStream(MediaStreamType.Subtitle, playlistItem.SubtitleStreamIndex.Value) : null; var audioStream = item.GetDefaultAudioStream(options.AudioStreamIndex ?? item.DefaultAudioStreamIndex); - if (audioStream != null) + if (audioStream is not null) { playlistItem.AudioStreamIndex = audioStream.Index; } // Collect candidate audio streams - ICollection<MediaStream> candidateAudioStreams = audioStream == null ? Array.Empty<MediaStream>() : new[] { audioStream }; + ICollection<MediaStream> candidateAudioStreams = audioStream is null ? Array.Empty<MediaStream>() : new[] { audioStream }; if (!options.AudioStreamIndex.HasValue || options.AudioStreamIndex < 0) { if (audioStream?.IsDefault == true) @@ -601,11 +612,21 @@ namespace MediaBrowser.Model.Dlna var videoStream = item.VideoStream; - var directPlayBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectPlay); - var directStreamBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectStream); - bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayBitrateEligibility == 0); - bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamBitrateEligibility == 0); - var transcodeReasons = directPlayBitrateEligibility | directStreamBitrateEligibility; + var bitrateLimitExceeded = IsBitrateLimitExceeded(item, options.GetMaxBitrate(false) ?? 0); + var isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || !bitrateLimitExceeded); + var isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || !bitrateLimitExceeded); + TranscodeReason transcodeReasons = 0; + + // Force transcode or remux for BD/DVD folders + if (item.VideoType == VideoType.Dvd || item.VideoType == VideoType.BluRay) + { + isEligibleForDirectPlay = false; + } + + if (bitrateLimitExceeded) + { + transcodeReasons = TranscodeReason.ContainerBitrateExceedsLimit; + } _logger.LogDebug( "Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}", @@ -614,7 +635,7 @@ namespace MediaBrowser.Model.Dlna isEligibleForDirectPlay, isEligibleForDirectStream); - DirectPlayProfile directPlayProfile = null; + DirectPlayProfile? directPlayProfile = null; if (isEligibleForDirectPlay || isEligibleForDirectStream) { // See if it can be direct played @@ -643,18 +664,18 @@ namespace MediaBrowser.Model.Dlna else if (directPlay == PlayMethod.DirectStream) { playlistItem.AudioStreamIndex = audioStream?.Index; - if (audioStream != null) + if (audioStream is not null) { - playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile.AudioCodec); + playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile?.AudioCodec); } SetStreamInfoOptionsFromDirectPlayProfile(options, item, playlistItem, directPlayProfile); - BuildStreamVideoItem(playlistItem, options, item, videoStream, audioStream, candidateAudioStreams, directPlayProfile.Container, directPlayProfile.VideoCodec, directPlayProfile.AudioCodec); + BuildStreamVideoItem(playlistItem, options, item, videoStream, audioStream, candidateAudioStreams, directPlayProfile?.Container, directPlayProfile?.VideoCodec, directPlayProfile?.AudioCodec); } - if (subtitleStream != null) + if (subtitleStream is not null) { - var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, directPlay.Value, _transcoderSupport, directPlayProfile.Container, null); + var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, directPlay.Value, _transcoderSupport, directPlayProfile?.Container, null); playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method; playlistItem.SubtitleFormat = subtitleProfile.Format; @@ -678,7 +699,7 @@ namespace MediaBrowser.Model.Dlna // Can't direct play, find the transcoding profile // If we do this for direct-stream we will overwrite the info var transcodingProfile = GetVideoTranscodeProfile(item, options, videoStream, audioStream, candidateAudioStreams, subtitleStream, playlistItem); - if (transcodingProfile != null) + if (transcodingProfile is not null) { SetStreamInfoOptionsFromTranscodingProfile(item, playlistItem, transcodingProfile); @@ -686,7 +707,7 @@ namespace MediaBrowser.Model.Dlna playlistItem.PlayMethod = PlayMethod.Transcode; - if (subtitleStream != null) + if (subtitleStream is not null) { var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, _transcoderSupport, transcodingProfile.Container, transcodingProfile.Protocol); @@ -702,7 +723,7 @@ namespace MediaBrowser.Model.Dlna } } - _logger.LogInformation( + _logger.LogDebug( "StreamBuilder.BuildVideoItem( Profile={0}, Path={1}, AudioStreamIndex={2}, SubtitleStreamIndex={3} ) => ( PlayMethod={4}, TranscodeReason={5} ) {6}", options.Profile.Name ?? "Anonymous Profile", item.Path ?? "Unknown path", @@ -716,7 +737,14 @@ namespace MediaBrowser.Model.Dlna return playlistItem; } - private TranscodingProfile GetVideoTranscodeProfile(MediaSourceInfo item, VideoOptions options, MediaStream videoStream, MediaStream audioStream, IEnumerable<MediaStream> candidateAudioStreams, MediaStream subtitleStream, StreamInfo playlistItem) + private TranscodingProfile? GetVideoTranscodeProfile( + MediaSourceInfo item, + MediaOptions options, + MediaStream? videoStream, + MediaStream? audioStream, + IEnumerable<MediaStream> candidateAudioStreams, + MediaStream? subtitleStream, + StreamInfo playlistItem) { if (!(item.SupportsTranscoding || item.SupportsDirectStream)) { @@ -729,7 +757,7 @@ namespace MediaBrowser.Model.Dlna if (options.AllowVideoStreamCopy) { // prefer direct copy profile - float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0; + float videoFramerate = videoStream?.AverageFrameRate ?? videoStream?.RealFrameRate ?? 0; TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp; int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio); int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video); @@ -740,7 +768,7 @@ namespace MediaBrowser.Model.Dlna if (ContainerProfile.ContainsContainer(videoCodecs, item.VideoStream?.Codec)) { - var videoCodec = transcodingProfile.VideoCodec; + var videoCodec = videoStream?.Codec; var container = transcodingProfile.Container; var appliedVideoConditions = options.Profile.CodecProfiles .Where(i => i.Type == CodecType.Video && @@ -763,12 +791,28 @@ namespace MediaBrowser.Model.Dlna return transcodingProfiles.FirstOrDefault(); } - private void BuildStreamVideoItem(StreamInfo playlistItem, VideoOptions options, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable<MediaStream> candidateAudioStreams, string container, string videoCodec, string audioCodec) + private void BuildStreamVideoItem( + StreamInfo playlistItem, + MediaOptions options, + MediaSourceInfo item, + MediaStream? videoStream, + MediaStream? audioStream, + IEnumerable<MediaStream> candidateAudioStreams, + string? container, + string? videoCodec, + string? audioCodec) { // Prefer matching video codecs var videoCodecs = ContainerProfile.SplitValue(videoCodec); + + // Enforce HLS video codec restrictions + if (string.Equals(playlistItem.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase)) + { + videoCodecs = videoCodecs.Where(codec => _supportedHlsVideoCodecs.Contains(codec)).ToArray(); + } + var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream?.Codec) ? videoStream?.Codec : null; - if (directVideoCodec != null) + if (directVideoCodec is not null) { // merge directVideoCodec to videoCodecs Array.Resize(ref videoCodecs, videoCodecs.Length + 1); @@ -780,12 +824,12 @@ namespace MediaBrowser.Model.Dlna // Copy video codec options as a starting point, this applies to transcode and direct-stream playlistItem.MaxFramerate = videoStream?.AverageFrameRate; var qualifier = videoStream?.Codec; - if (videoStream?.Level != null) + if (videoStream?.Level is not null) { playlistItem.SetOption(qualifier, "level", videoStream.Level.Value.ToString(CultureInfo.InvariantCulture)); } - if (videoStream?.BitDepth != null) + if (videoStream?.BitDepth is not null) { playlistItem.SetOption(qualifier, "videobitdepth", videoStream.BitDepth.Value.ToString(CultureInfo.InvariantCulture)); } @@ -795,16 +839,30 @@ namespace MediaBrowser.Model.Dlna playlistItem.SetOption(qualifier, "profile", videoStream.Profile.ToLowerInvariant()); } - if (videoStream != null && videoStream.Level != 0) + if (videoStream is not null && videoStream.Level != 0) { playlistItem.SetOption(qualifier, "level", videoStream.Level.ToString()); } // Prefer matching audio codecs, could do better here var audioCodecs = ContainerProfile.SplitValue(audioCodec); + + // Enforce HLS audio codec restrictions + if (string.Equals(playlistItem.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase)) + { + if (string.Equals(playlistItem.Container, "mp4", StringComparison.OrdinalIgnoreCase)) + { + audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsMp4.Contains(codec)).ToArray(); + } + else + { + audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsTs.Contains(codec)).ToArray(); + } + } + var directAudioStream = candidateAudioStreams.FirstOrDefault(stream => ContainerProfile.ContainsContainer(audioCodecs, stream.Codec)); playlistItem.AudioCodecs = audioCodecs; - if (directAudioStream != null) + if (directAudioStream is not null) { audioStream = directAudioStream; playlistItem.AudioStreamIndex = audioStream.Index; @@ -830,15 +888,15 @@ namespace MediaBrowser.Model.Dlna int? bitDepth = videoStream?.BitDepth; int? videoBitrate = videoStream?.BitRate; double? videoLevel = videoStream?.Level; - string videoProfile = videoStream?.Profile; - string videoRangeType = videoStream?.VideoRangeType; - float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0; + string? videoProfile = videoStream?.Profile; + string? videoRangeType = videoStream?.VideoRangeType; + float videoFramerate = videoStream is null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0; bool? isAnamorphic = videoStream?.IsAnamorphic; bool? isInterlaced = videoStream?.IsInterlaced; - string videoCodecTag = videoStream?.CodecTag; + string? videoCodecTag = videoStream?.CodecTag; bool? isAvc = videoStream?.IsAVC; - TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp; + TransportStreamTimestamp? timestamp = videoStream is null ? TransportStreamTimestamp.None : item.Timestamp; int? packetLength = videoStream?.PacketLength; int? refFrames = videoStream?.RefFrames; @@ -847,7 +905,7 @@ namespace MediaBrowser.Model.Dlna var appliedVideoConditions = options.Profile.CodecProfiles .Where(i => i.Type == CodecType.Video && - i.ContainsAnyCodec(videoCodec, container) && + i.ContainsAnyCodec(videoStream?.Codec, container) && i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))); var isFirstAppliedCodecProfile = true; foreach (var i in appliedVideoConditions) @@ -867,29 +925,29 @@ namespace MediaBrowser.Model.Dlna // Honor requested max channels playlistItem.GlobalMaxAudioChannels = options.MaxAudioChannels; - int audioBitrate = GetAudioBitrate(options.GetMaxBitrate(false) ?? 0, playlistItem.TargetAudioCodec, audioStream, playlistItem); + int audioBitrate = GetAudioBitrate(options.GetMaxBitrate(true) ?? 0, playlistItem.TargetAudioCodec, audioStream, playlistItem); playlistItem.AudioBitrate = Math.Min(playlistItem.AudioBitrate ?? audioBitrate, audioBitrate); - bool? isSecondaryAudio = audioStream == null ? null : item.IsSecondaryAudio(audioStream); - int? inputAudioBitrate = audioStream == null ? null : audioStream.BitRate; - int? audioChannels = audioStream == null ? null : audioStream.Channels; - string audioProfile = audioStream == null ? null : audioStream.Profile; - int? inputAudioSampleRate = audioStream == null ? null : audioStream.SampleRate; - int? inputAudioBitDepth = audioStream == null ? null : audioStream.BitDepth; + bool? isSecondaryAudio = audioStream is null ? null : item.IsSecondaryAudio(audioStream); + int? inputAudioBitrate = audioStream?.BitRate; + int? audioChannels = audioStream?.Channels; + string? audioProfile = audioStream?.Profile; + int? inputAudioSampleRate = audioStream?.SampleRate; + int? inputAudioBitDepth = audioStream?.BitDepth; var appliedAudioConditions = options.Profile.CodecProfiles .Where(i => i.Type == CodecType.VideoAudio && - i.ContainsAnyCodec(audioCodec, container) && + i.ContainsAnyCodec(audioStream?.Codec, container) && i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio))); isFirstAppliedCodecProfile = true; - foreach (var i in appliedAudioConditions) + foreach (var codecProfile in appliedAudioConditions) { var transcodingAudioCodecs = ContainerProfile.SplitValue(audioCodec); foreach (var transcodingAudioCodec in transcodingAudioCodecs) { - if (i.ContainsAnyCodec(transcodingAudioCodec, container)) + if (codecProfile.ContainsAnyCodec(transcodingAudioCodec, container)) { - ApplyTranscodingConditions(playlistItem, i.Conditions, transcodingAudioCodec, true, isFirstAppliedCodecProfile); + ApplyTranscodingConditions(playlistItem, codecProfile.Conditions, transcodingAudioCodec, true, isFirstAppliedCodecProfile); isFirstAppliedCodecProfile = false; break; } @@ -923,7 +981,7 @@ namespace MediaBrowser.Model.Dlna playlistItem?.TranscodeReasons); } - private static int GetDefaultAudioBitrate(string audioCodec, int? audioChannels) + private static int GetDefaultAudioBitrate(string? audioCodec, int? audioChannels) { if (!string.IsNullOrEmpty(audioCodec)) { @@ -956,16 +1014,16 @@ namespace MediaBrowser.Model.Dlna return 192000; } - private static int GetAudioBitrate(long maxTotalBitrate, string[] targetAudioCodecs, MediaStream audioStream, StreamInfo item) + private static int GetAudioBitrate(long maxTotalBitrate, string[] targetAudioCodecs, MediaStream? audioStream, StreamInfo item) { - string targetAudioCodec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0]; + string? targetAudioCodec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0]; int? targetAudioChannels = item.GetTargetAudioChannels(targetAudioCodec); int defaultBitrate; int encoderAudioBitrateLimit = int.MaxValue; - if (audioStream == null) + if (audioStream is null) { defaultBitrate = 192000; } @@ -982,7 +1040,7 @@ namespace MediaBrowser.Model.Dlna && audioStream.Channels.HasValue && audioStream.Channels.Value <= targetAudioChannels.Value && !string.IsNullOrEmpty(audioStream.Codec) - && targetAudioCodecs != null + && targetAudioCodecs is not null && targetAudioCodecs.Length > 0 && !Array.Exists(targetAudioCodecs, elem => string.Equals(audioStream.Codec, elem, StringComparison.OrdinalIgnoreCase))) { @@ -1017,31 +1075,38 @@ namespace MediaBrowser.Model.Dlna { return 128000; } - else if (totalBitrate <= 2000000) + + if (totalBitrate <= 2000000) { return 384000; } - else if (totalBitrate <= 3000000) + + if (totalBitrate <= 3000000) { return 448000; } - else if (totalBitrate <= 4000000) + + if (totalBitrate <= 4000000) { return 640000; } - else if (totalBitrate <= 5000000) + + if (totalBitrate <= 5000000) { return 768000; } - else if (totalBitrate <= 10000000) + + if (totalBitrate <= 10000000) { return 1536000; } - else if (totalBitrate <= 15000000) + + if (totalBitrate <= 15000000) { return 2304000; } - else if (totalBitrate <= 20000000) + + if (totalBitrate <= 20000000) { return 3584000; } @@ -1049,13 +1114,13 @@ namespace MediaBrowser.Model.Dlna return 7168000; } - private (DirectPlayProfile Profile, PlayMethod? PlayMethod, int? AudioStreamIndex, TranscodeReason TranscodeReasons) GetVideoDirectPlayProfile( - VideoOptions options, + private (DirectPlayProfile? Profile, PlayMethod? PlayMethod, int? AudioStreamIndex, TranscodeReason TranscodeReasons) GetVideoDirectPlayProfile( + MediaOptions options, MediaSourceInfo mediaSource, - MediaStream videoStream, - MediaStream audioStream, + MediaStream? videoStream, + MediaStream? audioStream, ICollection<MediaStream> candidateAudioStreams, - MediaStream subtitleStream, + MediaStream? subtitleStream, bool isEligibleForDirectPlay, bool isEligibleForDirectStream) { @@ -1078,15 +1143,15 @@ namespace MediaBrowser.Model.Dlna int? bitDepth = videoStream?.BitDepth; int? videoBitrate = videoStream?.BitRate; double? videoLevel = videoStream?.Level; - string videoProfile = videoStream?.Profile; - string videoRangeType = videoStream?.VideoRangeType; - float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0; + string? videoProfile = videoStream?.Profile; + string? videoRangeType = videoStream?.VideoRangeType; + float videoFramerate = videoStream is null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0; bool? isAnamorphic = videoStream?.IsAnamorphic; bool? isInterlaced = videoStream?.IsInterlaced; - string videoCodecTag = videoStream?.CodecTag; + string? videoCodecTag = videoStream?.CodecTag; bool? isAvc = videoStream?.IsAVC; - TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : mediaSource.Timestamp; + TransportStreamTimestamp? timestamp = videoStream is null ? TransportStreamTimestamp.None : mediaSource.Timestamp; int? packetLength = videoStream?.PacketLength; int? refFrames = videoStream?.RefFrames; @@ -1111,7 +1176,8 @@ namespace MediaBrowser.Model.Dlna profile, "VideoCodecProfile", profile.CodecProfiles - .Where(codecProfile => codecProfile.Type == CodecType.Video && codecProfile.ContainsAnyCodec(videoStream?.Codec, container) && + .Where(codecProfile => codecProfile.Type == CodecType.Video && + codecProfile.ContainsAnyCodec(videoStream?.Codec, container) && !checkVideoConditions(codecProfile.ApplyConditions).Any()) .SelectMany(codecProfile => checkVideoConditions(codecProfile.Conditions))); @@ -1119,7 +1185,7 @@ namespace MediaBrowser.Model.Dlna var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream)); TranscodeReason subtitleProfileReasons = 0; - if (subtitleStream != null) + if (subtitleStream is not null) { var subtitleProfile = GetSubtitleProfile(mediaSource, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.DirectPlay, _transcoderSupport, container, null); @@ -1141,7 +1207,6 @@ namespace MediaBrowser.Model.Dlna var reason = a & flag; if (reason != 0) { - a = reason; return index; } @@ -1151,6 +1216,8 @@ namespace MediaBrowser.Model.Dlna return index; }; + var containerSupported = false; + // Check DirectPlay profiles to see if it can be direct played var analyzedProfiles = profile.DirectPlayProfiles .Where(directPlayProfile => directPlayProfile.Type == DlnaProfileType.Video) @@ -1164,20 +1231,24 @@ namespace MediaBrowser.Model.Dlna { directPlayProfileReasons |= TranscodeReason.ContainerNotSupported; } + else + { + containerSupported = true; + } // Check video codec - string videoCodec = videoStream?.Codec; + string? videoCodec = videoStream?.Codec; if (!directPlayProfile.SupportsVideoCodec(videoCodec)) { directPlayProfileReasons |= TranscodeReason.VideoCodecNotSupported; } // Check audio codec - MediaStream selectedAudioStream = null; + MediaStream? selectedAudioStream = null; if (candidateAudioStreams.Any()) { selectedAudioStream = candidateAudioStreams.FirstOrDefault(audioStream => directPlayProfile.SupportsAudioCodec(audioStream.Codec)); - if (selectedAudioStream == null) + if (selectedAudioStream is null) { directPlayProfileReasons |= TranscodeReason.AudioCodecNotSupported; } @@ -1206,7 +1277,7 @@ namespace MediaBrowser.Model.Dlna { playMethod = PlayMethod.DirectPlay; } - else if (directStreamFailureReasons == 0 && isEligibleForDirectStream && mediaSource.SupportsDirectStream && directPlayProfile != null) + else if (directStreamFailureReasons == 0 && isEligibleForDirectStream && mediaSource.SupportsDirectStream) { playMethod = PlayMethod.DirectStream; } @@ -1218,17 +1289,20 @@ namespace MediaBrowser.Model.Dlna .ThenByDescending(analysis => analysis.Rank) .ThenBy(analysis => analysis.Order) .ToArray() - .ToLookup(analysis => analysis.Result.PlayMethod != null); + .ToLookup(analysis => analysis.Result.PlayMethod is not null); var profileMatch = analyzedProfiles[true] .Select(analysis => analysis.Result) .FirstOrDefault(); - if (profileMatch.Profile != null) + if (profileMatch.Profile is not null) { return profileMatch; } - var failureReasons = analyzedProfiles[false].Select(analysis => analysis.Result).FirstOrDefault().TranscodeReason; + var failureReasons = analyzedProfiles[false] + .Select(analysis => analysis.Result) + .Where(result => !containerSupported || (result.TranscodeReason & TranscodeReason.ContainerNotSupported) == 0) + .FirstOrDefault().TranscodeReason; if (failureReasons == 0) { failureReasons = TranscodeReason.DirectPlayError; @@ -1237,7 +1311,7 @@ namespace MediaBrowser.Model.Dlna return (Profile: null, PlayMethod: null, AudioStreamIndex: null, TranscodeReasons: failureReasons); } - private TranscodeReason CheckVideoAudioStreamDirectPlay(VideoOptions options, MediaSourceInfo mediaSource, string container, MediaStream audioStream) + 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)); @@ -1274,31 +1348,25 @@ namespace MediaBrowser.Model.Dlna mediaSource.Path ?? "Unknown path"); } - private TranscodeReason IsBitrateEligibleForDirectPlayback( - MediaSourceInfo item, - long maxBitrate, - VideoOptions options, - PlayMethod playMethod) - { - bool result = IsItemBitrateEligibleForDirectPlayback(item, maxBitrate, playMethod); - if (!result) - { - return TranscodeReason.ContainerBitrateExceedsLimit; - } - else - { - return 0; - } - } - + /// <summary> + /// Normalizes input container. + /// </summary> + /// <param name="mediaSource">The <see cref="MediaSourceInfo"/>.</param> + /// <param name="subtitleStream">The <see cref="MediaStream"/> of the subtitle stream.</param> + /// <param name="subtitleProfiles">The list of supported <see cref="SubtitleProfile"/>s.</param> + /// <param name="playMethod">The <see cref="PlayMethod"/>.</param> + /// <param name="transcoderSupport">The <see cref="ITranscoderSupport"/>.</param> + /// <param name="outputContainer">The output container.</param> + /// <param name="transcodingSubProtocol">The subtitle transoding protocol.</param> + /// <returns>The the normalized input container.</returns> public static SubtitleProfile GetSubtitleProfile( MediaSourceInfo mediaSource, MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, ITranscoderSupport transcoderSupport, - string outputContainer, - string transcodingSubProtocol) + string? outputContainer, + string? transcodingSubProtocol) { if (!subtitleStream.IsExternal && (playMethod != PlayMethod.Transcode || !string.Equals(transcodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase))) { @@ -1371,7 +1439,7 @@ namespace MediaBrowser.Model.Dlna }; } - private static bool IsSubtitleEmbedSupported(string transcodingContainer) + private static bool IsSubtitleEmbedSupported(string? transcodingContainer) { if (!string.IsNullOrEmpty(transcodingContainer)) { @@ -1383,7 +1451,8 @@ namespace MediaBrowser.Model.Dlna { return false; } - else if (ContainerProfile.ContainsContainer(normalizedContainers, "mkv") + + if (ContainerProfile.ContainsContainer(normalizedContainers, "mkv") || ContainerProfile.ContainsContainer(normalizedContainers, "matroska")) { return true; @@ -1393,7 +1462,7 @@ namespace MediaBrowser.Model.Dlna return false; } - private static SubtitleProfile GetExternalSubtitleProfile(MediaSourceInfo mediaSource, MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, ITranscoderSupport transcoderSupport, bool allowConversion) + private static SubtitleProfile? GetExternalSubtitleProfile(MediaSourceInfo mediaSource, MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, ITranscoderSupport transcoderSupport, bool allowConversion) { foreach (var profile in subtitleProfiles) { @@ -1448,67 +1517,60 @@ namespace MediaBrowser.Model.Dlna return null; } - private bool IsItemBitrateEligibleForDirectPlayback(MediaSourceInfo item, long maxBitrate, PlayMethod playMethod) + private bool IsBitrateLimitExceeded(MediaSourceInfo item, long maxBitrate) { - // Don't restrict by bitrate if coming from an external domain + // Don't restrict bitrate if item is remote. if (item.IsRemote) { - return true; + return false; } - long requestedMaxBitrate = maxBitrate > 0 ? maxBitrate : 1000000; + // If no maximum bitrate is set, default to no maximum bitrate. + long requestedMaxBitrate = maxBitrate > 0 ? maxBitrate : int.MaxValue; - // If we don't know the bitrate, then force a transcode if requested max bitrate is under 40 mbps + // If we don't know the item bitrate, then force a transcode if requested max bitrate is under 40 mbps int itemBitrate = item.Bitrate ?? 40000000; if (itemBitrate > requestedMaxBitrate) { _logger.LogDebug( - "Bitrate exceeds {PlayBackMethod} limit: media bitrate: {MediaBitrate}, max bitrate: {MaxBitrate}", - playMethod, + "Bitrate exceeds limit: media bitrate: {MediaBitrate}, max bitrate: {MaxBitrate}", itemBitrate, requestedMaxBitrate); - return false; - } - - return true; - } - - private static void ValidateInput(VideoOptions options) - { - ValidateAudioInput(options); - - if (options.AudioStreamIndex.HasValue && string.IsNullOrEmpty(options.MediaSourceId)) - { - throw new ArgumentException("MediaSourceId is required when a specific audio stream is requested"); + return true; } - if (options.SubtitleStreamIndex.HasValue && string.IsNullOrEmpty(options.MediaSourceId)) - { - throw new ArgumentException("MediaSourceId is required when a specific subtitle stream is requested"); - } + return false; } - private static void ValidateAudioInput(AudioOptions options) + private static void ValidateMediaOptions(MediaOptions options, bool isMediaSource) { if (options.ItemId.Equals(default)) { - throw new ArgumentException("ItemId is required"); + ArgumentException.ThrowIfNullOrEmpty(options.DeviceId); } - if (string.IsNullOrEmpty(options.DeviceId)) + if (options.Profile is null) { - throw new ArgumentException("DeviceId is required"); + throw new ArgumentException("Profile is required"); } - if (options.Profile == null) + if (options.MediaSources is null) { - throw new ArgumentException("Profile is required"); + throw new ArgumentException("MediaSources is required"); } - if (options.MediaSources == null) + if (isMediaSource) { - throw new ArgumentException("MediaSources is required"); + if (options.AudioStreamIndex.HasValue && string.IsNullOrEmpty(options.MediaSourceId)) + { + throw new ArgumentException("MediaSourceId is required when a specific audio stream is requested"); + } + + if (options.SubtitleStreamIndex.HasValue && string.IsNullOrEmpty(options.MediaSourceId)) + { + throw new ArgumentException("MediaSourceId is required when a specific subtitle stream is requested"); + } } } @@ -1524,7 +1586,8 @@ namespace MediaBrowser.Model.Dlna bool? isSecondaryAudio) { return codecProfiles - .Where(profile => profile.Type == CodecType.VideoAudio && profile.ContainsAnyCodec(codec, container) && + .Where(profile => profile.Type == CodecType.VideoAudio && + profile.ContainsAnyCodec(codec, container) && profile.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio))) .SelectMany(profile => profile.Conditions) .Where(condition => !ConditionProcessor.IsVideoAudioConditionSatisfied(condition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio)); @@ -1533,7 +1596,7 @@ namespace MediaBrowser.Model.Dlna private static IEnumerable<ProfileCondition> GetProfileConditionsForAudio( IEnumerable<CodecProfile> codecProfiles, string container, - string codec, + string? codec, int? audioChannels, int? audioBitrate, int? audioSampleRate, @@ -1541,7 +1604,8 @@ namespace MediaBrowser.Model.Dlna bool checkConditions) { var conditions = codecProfiles - .Where(profile => profile.Type == CodecType.Audio && profile.ContainsAnyCodec(codec, container) && + .Where(profile => profile.Type == CodecType.Audio && + profile.ContainsAnyCodec(codec, container) && profile.ApplyConditions.All(applyCondition => ConditionProcessor.IsAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth))) .SelectMany(profile => profile.Conditions); @@ -1553,7 +1617,7 @@ namespace MediaBrowser.Model.Dlna return conditions.Where(condition => !ConditionProcessor.IsAudioConditionSatisfied(condition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth)); } - private void ApplyTranscodingConditions(StreamInfo item, IEnumerable<ProfileCondition> conditions, string qualifier, bool enableQualifiedConditions, bool enableNonQualifiedConditions) + private void ApplyTranscodingConditions(StreamInfo item, IEnumerable<ProfileCondition> conditions, string? qualifier, bool enableQualifiedConditions, bool enableNonQualifiedConditions) { foreach (ProfileCondition condition in conditions) { @@ -1579,7 +1643,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var num)) { if (condition.Condition == ProfileConditionType.Equals) { @@ -1605,7 +1669,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var num)) { if (condition.Condition == ProfileConditionType.Equals) { @@ -1641,7 +1705,7 @@ namespace MediaBrowser.Model.Dlna } } - if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var num)) { if (condition.Condition == ProfileConditionType.Equals) { @@ -1765,7 +1829,7 @@ namespace MediaBrowser.Model.Dlna } } - if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var num)) { if (condition.Condition == ProfileConditionType.Equals) { @@ -1801,7 +1865,7 @@ namespace MediaBrowser.Model.Dlna } } - if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var num)) { if (condition.Condition == ProfileConditionType.Equals) { @@ -1827,8 +1891,8 @@ namespace MediaBrowser.Model.Dlna continue; } - // change from split by | to comma - // strip spaces to avoid having to encode + // Change from split by | to comma + // Strip spaces to avoid having to encode var values = value .Split('|', StringSplitOptions.RemoveEmptyEntries); @@ -1891,7 +1955,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var num)) { if (condition.Condition == ProfileConditionType.Equals) { @@ -1917,7 +1981,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var num)) { if (condition.Condition == ProfileConditionType.Equals) { @@ -1943,7 +2007,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + if (float.TryParse(value, CultureInfo.InvariantCulture, out var num)) { if (condition.Condition == ProfileConditionType.Equals) { @@ -1969,7 +2033,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var num)) { if (condition.Condition == ProfileConditionType.Equals) { @@ -1995,7 +2059,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var num)) { if (condition.Condition == ProfileConditionType.Equals) { @@ -2029,7 +2093,7 @@ namespace MediaBrowser.Model.Dlna } // Check audio codec - string audioCodec = audioStream?.Codec; + string? audioCodec = audioStream?.Codec; if (!profile.SupportsAudioCodec(audioCodec)) { return false; diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 5cfa2e7e3..a78a28e13 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -107,9 +107,8 @@ namespace MediaBrowser.Model.Dlna public string MediaSourceId => MediaSource?.Id; - public bool IsDirectStream => - PlayMethod == PlayMethod.DirectStream || - PlayMethod == PlayMethod.DirectPlay; + public bool IsDirectStream => MediaSource?.VideoType is not (VideoType.Dvd or VideoType.BluRay) + && PlayMethod is PlayMethod.DirectStream or PlayMethod.DirectPlay; /// <summary> /// Gets the audio stream that will be used. @@ -215,7 +214,7 @@ namespace MediaBrowser.Model.Dlna var stream = TargetVideoStream; return MaxFramerate.HasValue && !IsDirectStream ? MaxFramerate - : stream == null ? null : stream.AverageFrameRate ?? stream.RealFrameRate; + : stream is null ? null : stream.AverageFrameRate ?? stream.RealFrameRate; } } @@ -431,7 +430,7 @@ namespace MediaBrowser.Model.Dlna return totalBitrate.HasValue ? Convert.ToInt64(totalBitrate.Value * totalSeconds) : - (long?)null; + null; } return null; @@ -460,7 +459,7 @@ namespace MediaBrowser.Model.Dlna return !IsDirectStream ? defaultValue - : MediaSource == null ? defaultValue : MediaSource.Timestamp ?? TransportStreamTimestamp.None; + : MediaSource is null ? defaultValue : MediaSource.Timestamp ?? TransportStreamTimestamp.None; } } @@ -521,7 +520,7 @@ namespace MediaBrowser.Model.Dlna { var videoStream = TargetVideoStream; - if (videoStream != null && videoStream.Width.HasValue && videoStream.Height.HasValue) + if (videoStream is not null && videoStream.Width.HasValue && videoStream.Height.HasValue) { ImageDimensions size = new ImageDimensions(videoStream.Width.Value, videoStream.Height.Value); @@ -540,7 +539,7 @@ namespace MediaBrowser.Model.Dlna { var videoStream = TargetVideoStream; - if (videoStream != null && videoStream.Width.HasValue && videoStream.Height.HasValue) + if (videoStream is not null && videoStream.Width.HasValue && videoStream.Height.HasValue) { ImageDimensions size = new ImageDimensions(videoStream.Width.Value, videoStream.Height.Value); @@ -620,10 +619,7 @@ namespace MediaBrowser.Model.Dlna public string ToUrl(string baseUrl, string accessToken) { - if (string.IsNullOrEmpty(baseUrl)) - { - throw new ArgumentNullException(nameof(baseUrl)); - } + ArgumentException.ThrowIfNullOrEmpty(baseUrl); var list = new List<string>(); foreach (NameValuePair pair in BuildParams(this, accessToken)) @@ -664,10 +660,7 @@ namespace MediaBrowser.Model.Dlna private string GetUrl(string baseUrl, string queryString) { - if (string.IsNullOrEmpty(baseUrl)) - { - throw new ArgumentNullException(nameof(baseUrl)); - } + ArgumentException.ThrowIfNullOrEmpty(baseUrl); string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container; @@ -928,12 +921,8 @@ namespace MediaBrowser.Model.Dlna public int? GetTargetVideoBitDepth(string codec) { var value = GetOption(codec, "videobitdepth"); - if (string.IsNullOrEmpty(value)) - { - return null; - } - if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var result)) { return result; } @@ -944,12 +933,8 @@ namespace MediaBrowser.Model.Dlna public int? GetTargetAudioBitDepth(string codec) { var value = GetOption(codec, "audiobitdepth"); - if (string.IsNullOrEmpty(value)) - { - return null; - } - if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var result)) { return result; } @@ -960,12 +945,8 @@ namespace MediaBrowser.Model.Dlna public double? GetTargetVideoLevel(string codec) { var value = GetOption(codec, "level"); - if (string.IsNullOrEmpty(value)) - { - return null; - } - if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) + if (double.TryParse(value, CultureInfo.InvariantCulture, out var result)) { return result; } @@ -976,12 +957,8 @@ namespace MediaBrowser.Model.Dlna public int? GetTargetRefFrames(string codec) { var value = GetOption(codec, "maxrefframes"); - if (string.IsNullOrEmpty(value)) - { - return null; - } - if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var result)) { return result; } diff --git a/MediaBrowser.Model/Dlna/VideoOptions.cs b/MediaBrowser.Model/Dlna/VideoOptions.cs deleted file mode 100644 index 0cb80af54..000000000 --- a/MediaBrowser.Model/Dlna/VideoOptions.cs +++ /dev/null @@ -1,16 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Model.Dlna -{ - /// <summary> - /// Class VideoOptions. - /// </summary> - public class VideoOptions : AudioOptions - { - public int? AudioStreamIndex { get; set; } - - public int? SubtitleStreamIndex { get; set; } - - public bool AllowVideoStreamCopy { get; set; } - } -} diff --git a/MediaBrowser.Model/Drawing/DrawingUtils.cs b/MediaBrowser.Model/Drawing/DrawingUtils.cs index 556792768..2040d26bb 100644 --- a/MediaBrowser.Model/Drawing/DrawingUtils.cs +++ b/MediaBrowser.Model/Drawing/DrawingUtils.cs @@ -71,18 +71,18 @@ namespace MediaBrowser.Model.Drawing int? fillHeight) { // Return original size if input is invalid. - if ((fillWidth == null || fillWidth == 0) - && (fillHeight == null || fillHeight == 0)) + if ((fillWidth is null || fillWidth == 0) + && (fillHeight is null || fillHeight == 0)) { return size; } - if (fillWidth == null || fillWidth == 0) + if (fillWidth is null || fillWidth == 0) { fillWidth = 1; } - if (fillHeight == null || fillHeight == 0) + if (fillHeight is null || fillHeight == 0) { fillHeight = 1; } diff --git a/MediaBrowser.Model/Drawing/ImageResolution.cs b/MediaBrowser.Model/Drawing/ImageResolution.cs new file mode 100644 index 000000000..34738b799 --- /dev/null +++ b/MediaBrowser.Model/Drawing/ImageResolution.cs @@ -0,0 +1,52 @@ +namespace MediaBrowser.Model.Drawing; + +/// <summary> +/// Enum ImageResolution. +/// </summary> +public enum ImageResolution +{ + /// <summary> + /// MatchSource. + /// </summary> + MatchSource = 0, + + /// <summary> + /// 144p. + /// </summary> + P144 = 1, + + /// <summary> + /// 240p. + /// </summary> + P240 = 2, + + /// <summary> + /// 360p. + /// </summary> + P360 = 3, + + /// <summary> + /// 480p. + /// </summary> + P480 = 4, + + /// <summary> + /// 720p. + /// </summary> + P720 = 5, + + /// <summary> + /// 1080p. + /// </summary> + P1080 = 6, + + /// <summary> + /// 1440p. + /// </summary> + P1440 = 7, + + /// <summary> + /// 2160p. + /// </summary> + P2160 = 8 +} diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 2a86fded2..8fab1ca6d 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -780,6 +780,12 @@ namespace MediaBrowser.Model.Dto public string TimerId { get; set; } /// <summary> + /// Gets or sets the LUFS value. + /// </summary> + /// <value>The LUFS Value.</value> + public float? LUFS { get; set; } + + /// <summary> /// Gets or sets the current program. /// </summary> /// <value>The current program.</value> diff --git a/MediaBrowser.Model/Dto/BaseItemPerson.cs b/MediaBrowser.Model/Dto/BaseItemPerson.cs index 6b920b0ef..d3bcf492d 100644 --- a/MediaBrowser.Model/Dto/BaseItemPerson.cs +++ b/MediaBrowser.Model/Dto/BaseItemPerson.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Model.Entities; namespace MediaBrowser.Model.Dto @@ -33,7 +34,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the type. /// </summary> /// <value>The type.</value> - public string Type { get; set; } + public PersonKind Type { get; set; } /// <summary> /// Gets or sets the primary image tag. @@ -52,6 +53,6 @@ namespace MediaBrowser.Model.Dto /// </summary> /// <value><c>true</c> if this instance has primary image; otherwise, <c>false</c>.</value> [JsonIgnore] - public bool HasPrimaryImage => PrimaryImageTag != null; + public bool HasPrimaryImage => PrimaryImageTag is not null; } } diff --git a/MediaBrowser.Model/Dto/ImageOptions.cs b/MediaBrowser.Model/Dto/ImageOptions.cs deleted file mode 100644 index 3f4405f1e..000000000 --- a/MediaBrowser.Model/Dto/ImageOptions.cs +++ /dev/null @@ -1,111 +0,0 @@ -#nullable disable -using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.Entities; - -namespace MediaBrowser.Model.Dto -{ - /// <summary> - /// Class ImageOptions. - /// </summary> - public class ImageOptions - { - /// <summary> - /// Initializes a new instance of the <see cref="ImageOptions" /> class. - /// </summary> - public ImageOptions() - { - EnableImageEnhancers = true; - } - - /// <summary> - /// Gets or sets the type of the image. - /// </summary> - /// <value>The type of the image.</value> - public ImageType ImageType { get; set; } - - /// <summary> - /// Gets or sets the index of the image. - /// </summary> - /// <value>The index of the image.</value> - public int? ImageIndex { get; set; } - - /// <summary> - /// Gets or sets the width. - /// </summary> - /// <value>The width.</value> - public int? Width { get; set; } - - /// <summary> - /// Gets or sets the height. - /// </summary> - /// <value>The height.</value> - public int? Height { get; set; } - - /// <summary> - /// Gets or sets the width of the max. - /// </summary> - /// <value>The width of the max.</value> - public int? MaxWidth { get; set; } - - /// <summary> - /// Gets or sets the height of the max. - /// </summary> - /// <value>The height of the max.</value> - public int? MaxHeight { get; set; } - - /// <summary> - /// Gets or sets the quality. - /// </summary> - /// <value>The quality.</value> - public int? Quality { get; set; } - - /// <summary> - /// Gets or sets the image tag. - /// If set this will result in strong, unconditional response caching. - /// </summary> - /// <value>The hash.</value> - public string Tag { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether [crop whitespace]. - /// </summary> - /// <value><c>null</c> if [crop whitespace] contains no value, <c>true</c> if [crop whitespace]; otherwise, <c>false</c>.</value> - public bool? CropWhitespace { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether [enable image enhancers]. - /// </summary> - /// <value><c>true</c> if [enable image enhancers]; otherwise, <c>false</c>.</value> - public bool EnableImageEnhancers { get; set; } - - /// <summary> - /// Gets or sets the format. - /// </summary> - /// <value>The format.</value> - public ImageFormat? Format { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether [add played indicator]. - /// </summary> - /// <value><c>true</c> if [add played indicator]; otherwise, <c>false</c>.</value> - public bool AddPlayedIndicator { get; set; } - - /// <summary> - /// Gets or sets the percent played. - /// </summary> - /// <value>The percent played.</value> - public int? PercentPlayed { get; set; } - - /// <summary> - /// Gets or sets the un played count. - /// </summary> - /// <value>The un played count.</value> - public int? UnPlayedCount { get; set; } - - /// <summary> - /// Gets or sets the color of the background. - /// </summary> - /// <value>The color of the background.</value> - public string BackgroundColor { get; set; } - } -} diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index c348e83ae..520832aee 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -134,7 +134,7 @@ namespace MediaBrowser.Model.Dto public void InferTotalBitrate(bool force = false) { - if (MediaStreams == null) + if (MediaStreams is null) { return; } diff --git a/MediaBrowser.Model/Dto/UserDto.cs b/MediaBrowser.Model/Dto/UserDto.cs index 256d7b10f..05019741e 100644 --- a/MediaBrowser.Model/Dto/UserDto.cs +++ b/MediaBrowser.Model/Dto/UserDto.cs @@ -66,6 +66,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets a value indicating whether this instance has configured easy password. /// </summary> /// <value><c>true</c> if this instance has configured easy password; otherwise, <c>false</c>.</value> + [Obsolete("Easy Password has been replaced with Quick Connect")] public bool HasConfiguredEasyPassword { get; set; } /// <summary> diff --git a/MediaBrowser.Model/Entities/DownMixStereoAlgorithms.cs b/MediaBrowser.Model/Entities/DownMixStereoAlgorithms.cs new file mode 100644 index 000000000..385cd6a34 --- /dev/null +++ b/MediaBrowser.Model/Entities/DownMixStereoAlgorithms.cs @@ -0,0 +1,23 @@ +namespace MediaBrowser.Model.Entities; + +/// <summary> +/// An enum representing an algorithm to downmix 6ch+ to stereo. +/// Algorithms sourced from https://superuser.com/questions/852400/properly-downmix-5-1-to-stereo-using-ffmpeg/1410620#1410620. +/// </summary> +public enum DownMixStereoAlgorithms +{ + /// <summary> + /// No special algorithm. + /// </summary> + None = 0, + + /// <summary> + /// Algorithm by Dave_750. + /// </summary> + Dave750 = 1, + + /// <summary> + /// Nightmode Dialogue algorithm. + /// </summary> + NightmodeDialogue = 2 +} diff --git a/MediaBrowser.Model/Entities/IHasShares.cs b/MediaBrowser.Model/Entities/IHasShares.cs new file mode 100644 index 000000000..b34d1a037 --- /dev/null +++ b/MediaBrowser.Model/Entities/IHasShares.cs @@ -0,0 +1,12 @@ +namespace MediaBrowser.Model.Entities; + +/// <summary> +/// Interface for access to shares. +/// </summary> +public interface IHasShares +{ + /// <summary> + /// Gets or sets the shares. + /// </summary> + Share[] Shares { get; set; } +} diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 344ebaf80..47341f4e1 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -635,11 +635,12 @@ namespace MediaBrowser.Model.Entities // sub = external .sub file - return !codec.Contains("pgs", StringComparison.OrdinalIgnoreCase) && - !codec.Contains("dvd", StringComparison.OrdinalIgnoreCase) && - !codec.Contains("dvbsub", StringComparison.OrdinalIgnoreCase) && - !string.Equals(codec, "sub", StringComparison.OrdinalIgnoreCase) && - !string.Equals(codec, "dvb_subtitle", StringComparison.OrdinalIgnoreCase); + return !codec.Contains("pgs", StringComparison.OrdinalIgnoreCase) + && !codec.Contains("dvd", StringComparison.OrdinalIgnoreCase) + && !codec.Contains("dvbsub", StringComparison.OrdinalIgnoreCase) + && !string.Equals(codec, "sub", StringComparison.OrdinalIgnoreCase) + && !string.Equals(codec, "sup", StringComparison.OrdinalIgnoreCase) + && !string.Equals(codec, "dvb_subtitle", StringComparison.OrdinalIgnoreCase); } public bool SupportsSubtitleConversionTo(string toCodec) diff --git a/MediaBrowser.Model/Entities/MetadataProvider.cs b/MediaBrowser.Model/Entities/MetadataProvider.cs index 37e3d8864..bd8db9941 100644 --- a/MediaBrowser.Model/Entities/MetadataProvider.cs +++ b/MediaBrowser.Model/Entities/MetadataProvider.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - namespace MediaBrowser.Model.Entities { /// <summary> @@ -14,38 +12,78 @@ namespace MediaBrowser.Model.Entities Custom = 0, /// <summary> - /// The imdb. + /// The IMDb provider. /// </summary> Imdb = 2, /// <summary> - /// The TMDB. + /// The TMDb provider. /// </summary> Tmdb = 3, /// <summary> - /// The TVDB. + /// The TVDb provider. /// </summary> Tvdb = 4, /// <summary> - /// The tvcom. + /// The tvcom providerd. /// </summary> Tvcom = 5, /// <summary> - /// Tmdb Collection Id. + /// TMDb collection provider. /// </summary> TmdbCollection = 7, + + /// <summary> + /// The MusicBrainz album provider. + /// </summary> MusicBrainzAlbum = 8, + + /// <summary> + /// The MusicBrainz album artist provider. + /// </summary> MusicBrainzAlbumArtist = 9, + + /// <summary> + /// The MusicBrainz artist provider. + /// </summary> MusicBrainzArtist = 10, + + /// <summary> + /// The MusicBrainz release group provider. + /// </summary> MusicBrainzReleaseGroup = 11, + + /// <summary> + /// The Zap2It provider. + /// </summary> Zap2It = 12, + + /// <summary> + /// The TvRage provider. + /// </summary> TvRage = 15, + + /// <summary> + /// The AudioDb artist provider. + /// </summary> AudioDbArtist = 16, + + /// <summary> + /// The AudioDb collection provider. + /// </summary> AudioDbAlbum = 17, + + /// <summary> + /// The MusicBrainz track provider. + /// </summary> MusicBrainzTrack = 18, + + /// <summary> + /// The TvMaze provider. + /// </summary> TvMaze = 19 } } diff --git a/MediaBrowser.Model/Entities/ParentalRating.cs b/MediaBrowser.Model/Entities/ParentalRating.cs index 17b2868a3..c92640818 100644 --- a/MediaBrowser.Model/Entities/ParentalRating.cs +++ b/MediaBrowser.Model/Entities/ParentalRating.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Model.Entities { } - public ParentalRating(string name, int value) + public ParentalRating(string name, int? value) { Name = name; Value = value; @@ -28,6 +28,6 @@ namespace MediaBrowser.Model.Entities /// Gets or sets the value. /// </summary> /// <value>The value.</value> - public int Value { get; set; } + public int? Value { get; set; } } } diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs index d3b8400f3..cf453d62c 100644 --- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs +++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs @@ -55,7 +55,7 @@ namespace MediaBrowser.Model.Entities { ArgumentNullException.ThrowIfNull(instance); - if (instance.ProviderIds == null) + if (instance.ProviderIds is null) { id = null; return false; diff --git a/MediaBrowser.Model/Entities/Share.cs b/MediaBrowser.Model/Entities/Share.cs new file mode 100644 index 000000000..186aad189 --- /dev/null +++ b/MediaBrowser.Model/Entities/Share.cs @@ -0,0 +1,17 @@ +namespace MediaBrowser.Model.Entities; + +/// <summary> +/// Class to hold data on sharing permissions. +/// </summary> +public class Share +{ + /// <summary> + /// Gets or sets the user id. + /// </summary> + public string? UserId { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the user has edit permissions. + /// </summary> + public bool CanEdit { get; set; } +} diff --git a/MediaBrowser.Model/Extensions/EnumerableExtensions.cs b/MediaBrowser.Model/Extensions/EnumerableExtensions.cs index a5a6b18aa..c6d1f3900 100644 --- a/MediaBrowser.Model/Extensions/EnumerableExtensions.cs +++ b/MediaBrowser.Model/Extensions/EnumerableExtensions.cs @@ -24,24 +24,27 @@ namespace MediaBrowser.Model.Extensions requestedLanguage = "en"; } - var isRequestedLanguageEn = string.Equals(requestedLanguage, "en", StringComparison.OrdinalIgnoreCase); - return remoteImageInfos.OrderByDescending(i => { + // Image priority ordering: + // - Images that match the requested language + // - Images with no language + // - TODO: Images that match the original language + // - Images in English + // - Images that don't match the requested language + if (string.Equals(requestedLanguage, i.Language, StringComparison.OrdinalIgnoreCase)) { - return 3; + return 4; } if (string.IsNullOrEmpty(i.Language)) { - // Assume empty image language is likely to be English. - return isRequestedLanguageEn ? 3 : 2; + return 3; } - if (!isRequestedLanguageEn && string.Equals(i.Language, "en", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(i.Language, "en", StringComparison.OrdinalIgnoreCase)) { - // Prioritize English over non-requested languages. return 2; } diff --git a/MediaBrowser.Model/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs index e00157dce..02a29e7fa 100644 --- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs +++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs @@ -30,8 +30,9 @@ namespace MediaBrowser.Model.Globalization /// Gets the rating level. /// </summary> /// <param name="rating">The rating.</param> + /// <param name="countryCode">The optional two letter ISO language string.</param> /// <returns><see cref="int" /> or <c>null</c>.</returns> - int? GetRatingLevel(string rating); + int? GetRatingLevel(string rating, string? countryCode = null); /// <summary> /// Gets the localized string. diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs index 4cece941c..25e5c7796 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs @@ -40,5 +40,9 @@ namespace MediaBrowser.Model.LiveTv public string RecordingPostProcessor { get; set; } public string RecordingPostProcessorArguments { get; set; } + + public bool SaveRecordingNFO { get; set; } = true; + + public bool SaveRecordingImages { get; set; } = true; } } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 4172e9825..9a5804485 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -14,7 +14,7 @@ </PropertyGroup> <PropertyGroup> - <TargetFramework>net6.0</TargetFramework> + <TargetFramework>net7.0</TargetFramework> <GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateDocumentationFile>true</GenerateDocumentationFile> <PublishRepositoryUrl>true</PublishRepositoryUrl> @@ -24,7 +24,7 @@ </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> - <TreatWarningsAsErrors>false</TreatWarningsAsErrors> + <CodeAnalysisTreatWarningsAsErrors>false</CodeAnalysisTreatWarningsAsErrors> </PropertyGroup> <PropertyGroup Condition=" '$(Stability)'=='Unstable'"> @@ -33,14 +33,14 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" /> - <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.3" /> - <PackageReference Include="MimeTypes" Version="2.4.0"> + <PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" /> + <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" /> + <PackageReference Include="MimeTypes"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> - <PackageReference Include="System.Globalization" Version="4.3.0" /> - <PackageReference Include="System.Text.Json" Version="6.0.7" /> + <PackageReference Include="System.Globalization" /> + <PackageReference Include="System.Text.Json" /> </ItemGroup> <ItemGroup> @@ -49,13 +49,13 @@ <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference> - <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" /> - <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> + <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> </ItemGroup> <ItemGroup> <ProjectReference Include="../Jellyfin.Data/Jellyfin.Data.csproj" /> diff --git a/MediaBrowser.Model/MediaInfo/AudioCodec.cs b/MediaBrowser.Model/MediaInfo/AudioCodec.cs index 7b83b1b9d..4c22af449 100644 --- a/MediaBrowser.Model/MediaInfo/AudioCodec.cs +++ b/MediaBrowser.Model/MediaInfo/AudioCodec.cs @@ -17,11 +17,13 @@ namespace MediaBrowser.Model.MediaInfo { return "Dolby Digital"; } - else if (string.Equals(codec, "eac3", StringComparison.OrdinalIgnoreCase)) + + if (string.Equals(codec, "eac3", StringComparison.OrdinalIgnoreCase)) { return "Dolby Digital+"; } - else if (string.Equals(codec, "dca", StringComparison.OrdinalIgnoreCase)) + + if (string.Equals(codec, "dca", StringComparison.OrdinalIgnoreCase)) { return "DTS"; } diff --git a/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs b/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs index 83f982a5c..d546ffccd 100644 --- a/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs +++ b/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs @@ -1,39 +1,41 @@ #nullable disable -#pragma warning disable CS1591 using MediaBrowser.Model.Entities; -namespace MediaBrowser.Model.MediaInfo +namespace MediaBrowser.Model.MediaInfo; + +/// <summary> +/// Represents the result of BDInfo output. +/// </summary> +public class BlurayDiscInfo { /// <summary> - /// Represents the result of BDInfo output. + /// Gets or sets the media streams. /// </summary> - public class BlurayDiscInfo - { - /// <summary> - /// Gets or sets the media streams. - /// </summary> - /// <value>The media streams.</value> - public MediaStream[] MediaStreams { get; set; } + /// <value>The media streams.</value> + public MediaStream[] MediaStreams { get; set; } - /// <summary> - /// Gets or sets the run time ticks. - /// </summary> - /// <value>The run time ticks.</value> - public long? RunTimeTicks { get; set; } + /// <summary> + /// Gets or sets the run time ticks. + /// </summary> + /// <value>The run time ticks.</value> + public long? RunTimeTicks { get; set; } - /// <summary> - /// Gets or sets the files. - /// </summary> - /// <value>The files.</value> - public string[] Files { get; set; } + /// <summary> + /// Gets or sets the files. + /// </summary> + /// <value>The files.</value> + public string[] Files { get; set; } - public string PlaylistName { get; set; } + /// <summary> + /// Gets or sets the playlist name. + /// </summary> + /// <value>The playlist name.</value> + public string PlaylistName { get; set; } - /// <summary> - /// Gets or sets the chapters. - /// </summary> - /// <value>The chapters.</value> - public double[] Chapters { get; set; } - } + /// <summary> + /// Gets or sets the chapters. + /// </summary> + /// <value>The chapters.</value> + public double[] Chapters { get; set; } } diff --git a/MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs b/MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs index 5b7d1d03c..d39725301 100644 --- a/MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs +++ b/MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs @@ -1,15 +1,14 @@ -namespace MediaBrowser.Model.MediaInfo +namespace MediaBrowser.Model.MediaInfo; + +/// <summary> +/// Interface IBlurayExaminer. +/// </summary> +public interface IBlurayExaminer { /// <summary> - /// Interface IBlurayExaminer. + /// Gets the disc info. /// </summary> - public interface IBlurayExaminer - { - /// <summary> - /// Gets the disc info. - /// </summary> - /// <param name="path">The path.</param> - /// <returns>BlurayDiscInfo.</returns> - BlurayDiscInfo GetDiscInfo(string path); - } + /// <param name="path">The path.</param> + /// <returns>BlurayDiscInfo.</returns> + BlurayDiscInfo GetDiscInfo(string path); } diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs index 3b03466e9..5a1871070 100644 --- a/MediaBrowser.Model/Net/MimeTypes.cs +++ b/MediaBrowser.Model/Net/MimeTypes.cs @@ -117,7 +117,9 @@ namespace MediaBrowser.Model.Net // Type image { "image/jpeg", ".jpg" }, + { "image/tiff", ".tiff" }, { "image/x-png", ".png" }, + { "image/x-icon", ".ico" }, // Type text { "text/plain", ".txt" }, @@ -140,10 +142,7 @@ namespace MediaBrowser.Model.Net [return: NotNullIfNotNull("defaultValue")] public static string? GetMimeType(string filename, string? defaultValue = null) { - if (filename.Length == 0) - { - throw new ArgumentException("String can't be empty.", nameof(filename)); - } + ArgumentException.ThrowIfNullOrEmpty(filename); var ext = Path.GetExtension(filename); @@ -168,10 +167,7 @@ namespace MediaBrowser.Model.Net public static string? ToExtension(string mimeType) { - if (mimeType.Length == 0) - { - throw new ArgumentException("String can't be empty.", nameof(mimeType)); - } + ArgumentException.ThrowIfNullOrEmpty(mimeType); // handle text/html; charset=UTF-8 mimeType = mimeType.AsSpan().LeftPart(';').ToString(); @@ -184,5 +180,8 @@ namespace MediaBrowser.Model.Net var extension = Model.MimeTypes.GetMimeTypeExtensions(mimeType).FirstOrDefault(); return string.IsNullOrEmpty(extension) ? null : "." + extension; } + + public static bool IsImage(ReadOnlySpan<char> mimeType) + => mimeType.StartsWith("image/", StringComparison.OrdinalIgnoreCase); } } diff --git a/MediaBrowser.Model/Net/WebSocketMessage.cs b/MediaBrowser.Model/Net/WebSocketMessage.cs deleted file mode 100644 index b00158cb3..000000000 --- a/MediaBrowser.Model/Net/WebSocketMessage.cs +++ /dev/null @@ -1,31 +0,0 @@ -#nullable disable -#pragma warning disable CS1591 - -using System; -using MediaBrowser.Model.Session; - -namespace MediaBrowser.Model.Net -{ - /// <summary> - /// Class WebSocketMessage. - /// </summary> - /// <typeparam name="T">The type of the data.</typeparam> - public class WebSocketMessage<T> - { - /// <summary> - /// Gets or sets the type of the message. - /// </summary> - /// <value>The type of the message.</value> - public SessionMessageType MessageType { get; set; } - - public Guid MessageId { get; set; } - - public string ServerId { get; set; } - - /// <summary> - /// Gets or sets the data. - /// </summary> - /// <value>The data.</value> - public T Data { get; set; } - } -} diff --git a/MediaBrowser.Model/Notifications/NotificationLevel.cs b/MediaBrowser.Model/Notifications/NotificationLevel.cs deleted file mode 100644 index 14fead3f0..000000000 --- a/MediaBrowser.Model/Notifications/NotificationLevel.cs +++ /dev/null @@ -1,11 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Model.Notifications -{ - public enum NotificationLevel - { - Normal = 0, - Warning = 1, - Error = 2 - } -} diff --git a/MediaBrowser.Model/Notifications/NotificationOption.cs b/MediaBrowser.Model/Notifications/NotificationOption.cs deleted file mode 100644 index 58aecb3d3..000000000 --- a/MediaBrowser.Model/Notifications/NotificationOption.cs +++ /dev/null @@ -1,55 +0,0 @@ -#pragma warning disable CA1819 // Properties should not return arrays -#pragma warning disable CS1591 - -using System; - -namespace MediaBrowser.Model.Notifications -{ - public class NotificationOption - { - public NotificationOption(string type) - { - Type = type; - DisabledServices = Array.Empty<string>(); - DisabledMonitorUsers = Array.Empty<string>(); - SendToUsers = Array.Empty<string>(); - } - - public NotificationOption() - { - DisabledServices = Array.Empty<string>(); - DisabledMonitorUsers = Array.Empty<string>(); - SendToUsers = Array.Empty<string>(); - } - - public string? Type { get; set; } - - /// <summary> - /// Gets or sets user Ids to not monitor (it's opt out). - /// </summary> - public string[] DisabledMonitorUsers { get; set; } - - /// <summary> - /// Gets or sets user Ids to send to (if SendToUserMode == Custom). - /// </summary> - public string[] SendToUsers { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this <see cref="NotificationOption"/> is enabled. - /// </summary> - /// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value> - public bool Enabled { get; set; } - - /// <summary> - /// Gets or sets the disabled services. - /// </summary> - /// <value>The disabled services.</value> - public string[] DisabledServices { get; set; } - - /// <summary> - /// Gets or sets the send to user mode. - /// </summary> - /// <value>The send to user mode.</value> - public SendToUserType SendToUserMode { get; set; } - } -} diff --git a/MediaBrowser.Model/Notifications/NotificationOptions.cs b/MediaBrowser.Model/Notifications/NotificationOptions.cs deleted file mode 100644 index d1b5491bd..000000000 --- a/MediaBrowser.Model/Notifications/NotificationOptions.cs +++ /dev/null @@ -1,131 +0,0 @@ -#nullable disable -#pragma warning disable CS1591 - -using System; -using Jellyfin.Data.Entities; -using Jellyfin.Data.Enums; -using Jellyfin.Extensions; - -namespace MediaBrowser.Model.Notifications -{ - public class NotificationOptions - { - public NotificationOptions() - { - Options = new[] - { - new NotificationOption(NotificationType.TaskFailed.ToString()) - { - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption(NotificationType.ServerRestartRequired.ToString()) - { - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption(NotificationType.ApplicationUpdateAvailable.ToString()) - { - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption(NotificationType.ApplicationUpdateInstalled.ToString()) - { - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption(NotificationType.PluginUpdateInstalled.ToString()) - { - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption(NotificationType.PluginUninstalled.ToString()) - { - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption(NotificationType.InstallationFailed.ToString()) - { - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption(NotificationType.PluginInstalled.ToString()) - { - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption(NotificationType.PluginError.ToString()) - { - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption(NotificationType.UserLockedOut.ToString()) - { - Enabled = true, - SendToUserMode = SendToUserType.Admins - } - }; - } - - public NotificationOption[] Options { get; set; } - - public NotificationOption GetOptions(string type) - { - foreach (NotificationOption i in Options) - { - if (string.Equals(type, i.Type, StringComparison.OrdinalIgnoreCase)) - { - return i; - } - } - - return null; - } - - public bool IsEnabled(string type) - { - NotificationOption opt = GetOptions(type); - - return opt != null && opt.Enabled; - } - - public bool IsServiceEnabled(string service, string notificationType) - { - NotificationOption opt = GetOptions(notificationType); - - return opt == null - || !opt.DisabledServices.Contains(service, StringComparison.OrdinalIgnoreCase); - } - - public bool IsEnabledToMonitorUser(string type, Guid userId) - { - NotificationOption opt = GetOptions(type); - - return opt != null - && opt.Enabled - && !opt.DisabledMonitorUsers.Contains(userId.ToString("N"), StringComparison.OrdinalIgnoreCase); - } - - public bool IsEnabledToSendToUser(string type, string userId, User user) - { - NotificationOption opt = GetOptions(type); - - if (opt != null && opt.Enabled) - { - if (opt.SendToUserMode == SendToUserType.All) - { - return true; - } - - if (opt.SendToUserMode == SendToUserType.Admins && user.HasPermission(PermissionKind.IsAdministrator)) - { - return true; - } - - return opt.SendToUsers.Contains(userId, StringComparison.OrdinalIgnoreCase); - } - - return false; - } - } -} diff --git a/MediaBrowser.Model/Notifications/NotificationRequest.cs b/MediaBrowser.Model/Notifications/NotificationRequest.cs deleted file mode 100644 index 622c50cd8..000000000 --- a/MediaBrowser.Model/Notifications/NotificationRequest.cs +++ /dev/null @@ -1,35 +0,0 @@ -#nullable disable -#pragma warning disable CS1591 - -using System; - -namespace MediaBrowser.Model.Notifications -{ - public class NotificationRequest - { - public NotificationRequest() - { - UserIds = Array.Empty<Guid>(); - Date = DateTime.UtcNow; - } - - public string Name { get; set; } - - public string Description { get; set; } - - public string Url { get; set; } - - public NotificationLevel Level { get; set; } - - public Guid[] UserIds { get; set; } - - public DateTime Date { get; set; } - - /// <summary> - /// Gets or sets the corresponding type name used in configuration. Not for display. - /// </summary> - public string NotificationType { get; set; } - - public SendToUserType? SendToUserMode { get; set; } - } -} diff --git a/MediaBrowser.Model/Notifications/NotificationTypeInfo.cs b/MediaBrowser.Model/Notifications/NotificationTypeInfo.cs deleted file mode 100644 index 402fbe81a..000000000 --- a/MediaBrowser.Model/Notifications/NotificationTypeInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -#nullable disable -#pragma warning disable CS1591 - -namespace MediaBrowser.Model.Notifications -{ - public class NotificationTypeInfo - { - public string Type { get; set; } - - public string Name { get; set; } - - public bool Enabled { get; set; } - - public string Category { get; set; } - - public bool IsBasedOnUserEvent { get; set; } - } -} diff --git a/MediaBrowser.Model/Notifications/SendToUserType.cs b/MediaBrowser.Model/Notifications/SendToUserType.cs deleted file mode 100644 index 65fc4e1ab..000000000 --- a/MediaBrowser.Model/Notifications/SendToUserType.cs +++ /dev/null @@ -1,11 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Model.Notifications -{ - public enum SendToUserType - { - All = 0, - Admins = 1, - Custom = 2 - } -} diff --git a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs index e8ee49403..847269716 100644 --- a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs +++ b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs @@ -1,19 +1,36 @@ -#nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Model.Playlists; -namespace MediaBrowser.Model.Playlists +/// <summary> +/// A playlist creation request. +/// </summary> +public class PlaylistCreationRequest { - public class PlaylistCreationRequest - { - public string Name { get; set; } + /// <summary> + /// Gets or sets the name. + /// </summary> + public string? Name { get; set; } + + /// <summary> + /// Gets or sets the list of items. + /// </summary> + public IReadOnlyList<Guid> ItemIdList { get; set; } = Array.Empty<Guid>(); - public IReadOnlyList<Guid> ItemIdList { get; set; } = Array.Empty<Guid>(); + /// <summary> + /// Gets or sets the media type. + /// </summary> + public string? MediaType { get; set; } - public string MediaType { get; set; } + /// <summary> + /// Gets or sets the user id. + /// </summary> + public Guid UserId { get; set; } - public Guid UserId { get; set; } - } + /// <summary> + /// Gets or sets the shares. + /// </summary> + public Share[]? Shares { get; set; } } diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index e6c3a6c26..6fa1d778a 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -126,7 +126,7 @@ namespace MediaBrowser.Model.Querying ProductionLocations, /// <summary> - /// Imdb, tmdb, etc. + /// The ids from IMDb, TMDb, etc. /// </summary> ProviderIds, diff --git a/MediaBrowser.Model/Querying/ItemSortBy.cs b/MediaBrowser.Model/Querying/ItemSortBy.cs index 470507c53..1a7c9a63b 100644 --- a/MediaBrowser.Model/Querying/ItemSortBy.cs +++ b/MediaBrowser.Model/Querying/ItemSortBy.cs @@ -154,5 +154,10 @@ namespace MediaBrowser.Model.Querying /// The similarity score. /// </summary> public const string SimilarityScore = "SimilarityScore"; + + /// <summary> + /// The search score. + /// </summary> + public const string SearchScore = "SearchScore"; } } diff --git a/MediaBrowser.Model/SyncPlay/GroupUpdate.cs b/MediaBrowser.Model/SyncPlay/GroupUpdate.cs index 6f159d653..ec67d7ea8 100644 --- a/MediaBrowser.Model/SyncPlay/GroupUpdate.cs +++ b/MediaBrowser.Model/SyncPlay/GroupUpdate.cs @@ -1,42 +1,30 @@ using System; -namespace MediaBrowser.Model.SyncPlay +namespace MediaBrowser.Model.SyncPlay; + +/// <summary> +/// Group update without data. +/// </summary> +public abstract class GroupUpdate { /// <summary> - /// Class GroupUpdate. + /// Initializes a new instance of the <see cref="GroupUpdate"/> class. /// </summary> - /// <typeparam name="T">The type of the data of the message.</typeparam> - public class GroupUpdate<T> + /// <param name="groupId">The group identifier.</param> + protected GroupUpdate(Guid groupId) { - /// <summary> - /// Initializes a new instance of the <see cref="GroupUpdate{T}"/> class. - /// </summary> - /// <param name="groupId">The group identifier.</param> - /// <param name="type">The update type.</param> - /// <param name="data">The update data.</param> - public GroupUpdate(Guid groupId, GroupUpdateType type, T data) - { - GroupId = groupId; - Type = type; - Data = data; - } - - /// <summary> - /// Gets the group identifier. - /// </summary> - /// <value>The group identifier.</value> - public Guid GroupId { get; } + GroupId = groupId; + } - /// <summary> - /// Gets the update type. - /// </summary> - /// <value>The update type.</value> - public GroupUpdateType Type { get; } + /// <summary> + /// Gets the group identifier. + /// </summary> + /// <value>The group identifier.</value> + public Guid GroupId { get; } - /// <summary> - /// Gets the update data. - /// </summary> - /// <value>The update data.</value> - public T Data { get; } - } + /// <summary> + /// Gets the update type. + /// </summary> + /// <value>The update type.</value> + public GroupUpdateType Type { get; init; } } diff --git a/MediaBrowser.Model/SyncPlay/GroupUpdateOfT.cs b/MediaBrowser.Model/SyncPlay/GroupUpdateOfT.cs new file mode 100644 index 000000000..25cd44461 --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/GroupUpdateOfT.cs @@ -0,0 +1,31 @@ +#pragma warning disable SA1649 + +using System; + +namespace MediaBrowser.Model.SyncPlay; + +/// <summary> +/// Class GroupUpdate. +/// </summary> +/// <typeparam name="T">The type of the data of the message.</typeparam> +public class GroupUpdate<T> : GroupUpdate +{ + /// <summary> + /// Initializes a new instance of the <see cref="GroupUpdate{T}"/> class. + /// </summary> + /// <param name="groupId">The group identifier.</param> + /// <param name="type">The update type.</param> + /// <param name="data">The update data.</param> + public GroupUpdate(Guid groupId, GroupUpdateType type, T data) + : base(groupId) + { + Data = data; + Type = type; + } + + /// <summary> + /// Gets the update data. + /// </summary> + /// <value>The update data.</value> + public T Data { get; } +} diff --git a/MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs b/MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs index cce99c77d..376d926c9 100644 --- a/MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs +++ b/MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Model.SyncPlay /// <param name="isPlaying">The playing item status.</param> /// <param name="shuffleMode">The shuffle mode.</param> /// <param name="repeatMode">The repeat mode.</param> - public PlayQueueUpdate(PlayQueueUpdateReason reason, DateTime lastUpdate, IReadOnlyList<QueueItem> playlist, int playingItemIndex, long startPositionTicks, bool isPlaying, GroupShuffleMode shuffleMode, GroupRepeatMode repeatMode) + public PlayQueueUpdate(PlayQueueUpdateReason reason, DateTime lastUpdate, IReadOnlyList<SyncPlayQueueItem> playlist, int playingItemIndex, long startPositionTicks, bool isPlaying, GroupShuffleMode shuffleMode, GroupRepeatMode repeatMode) { Reason = reason; LastUpdate = lastUpdate; @@ -47,7 +47,7 @@ namespace MediaBrowser.Model.SyncPlay /// Gets the playlist. /// </summary> /// <value>The playlist.</value> - public IReadOnlyList<QueueItem> Playlist { get; } + public IReadOnlyList<SyncPlayQueueItem> Playlist { get; } /// <summary> /// Gets the playing item index in the playlist. diff --git a/MediaBrowser.Model/SyncPlay/QueueItem.cs b/MediaBrowser.Model/SyncPlay/SyncPlayQueueItem.cs index a6dcc109e..da81fecbd 100644 --- a/MediaBrowser.Model/SyncPlay/QueueItem.cs +++ b/MediaBrowser.Model/SyncPlay/SyncPlayQueueItem.cs @@ -5,13 +5,13 @@ namespace MediaBrowser.Model.SyncPlay /// <summary> /// Class QueueItem. /// </summary> - public class QueueItem + public class SyncPlayQueueItem { /// <summary> - /// Initializes a new instance of the <see cref="QueueItem"/> class. + /// Initializes a new instance of the <see cref="SyncPlayQueueItem"/> class. /// </summary> /// <param name="itemId">The item identifier.</param> - public QueueItem(Guid itemId) + public SyncPlayQueueItem(Guid itemId) { ItemId = itemId; } diff --git a/MediaBrowser.Model/System/OperatingSystemId.cs b/MediaBrowser.Model/System/OperatingSystemId.cs deleted file mode 100644 index 2e417f6b5..000000000 --- a/MediaBrowser.Model/System/OperatingSystemId.cs +++ /dev/null @@ -1,12 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Model.System -{ - public enum OperatingSystemId - { - Windows, - Linux, - Darwin, - BSD - } -} diff --git a/MediaBrowser.Model/System/PublicSystemInfo.cs b/MediaBrowser.Model/System/PublicSystemInfo.cs index 53030843a..31a895642 100644 --- a/MediaBrowser.Model/System/PublicSystemInfo.cs +++ b/MediaBrowser.Model/System/PublicSystemInfo.cs @@ -1,6 +1,8 @@ #nullable disable #pragma warning disable CS1591 +using System; + namespace MediaBrowser.Model.System { public class PublicSystemInfo @@ -32,7 +34,8 @@ namespace MediaBrowser.Model.System /// Gets or sets the operating system. /// </summary> /// <value>The operating system.</value> - public string OperatingSystem { get; set; } + [Obsolete("This is no longer set")] + public string OperatingSystem { get; set; } = string.Empty; /// <summary> /// Gets or sets the id. diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index a82c1c8c0..bd0099af7 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -42,7 +42,8 @@ namespace MediaBrowser.Model.System /// Gets or sets the display name of the operating system. /// </summary> /// <value>The display name of the operating system.</value> - public string OperatingSystemDisplayName { get; set; } + [Obsolete("This is no longer set")] + public string OperatingSystemDisplayName { get; set; } = string.Empty; /// <summary> /// Gets or sets the package name. @@ -79,8 +80,9 @@ namespace MediaBrowser.Model.System /// <summary> /// Gets or sets a value indicating whether this instance can self restart. /// </summary> - /// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value> - public bool CanSelfRestart { get; set; } + /// <value><c>true</c>.</value> + [Obsolete("This is always true")] + public bool CanSelfRestart { get; set; } = true; public bool CanLaunchWebBrowser { get; set; } @@ -136,6 +138,7 @@ namespace MediaBrowser.Model.System [Obsolete("This isn't set correctly anymore")] public FFmpegLocation EncoderLocation { get; set; } - public Architecture SystemArchitecture { get; set; } + [Obsolete("This is no longer set")] + public Architecture SystemArchitecture { get; set; } = Architecture.X64; } } diff --git a/MediaBrowser.Model/Tasks/ITaskManager.cs b/MediaBrowser.Model/Tasks/ITaskManager.cs index 13bebc479..5b55667e8 100644 --- a/MediaBrowser.Model/Tasks/ITaskManager.cs +++ b/MediaBrowser.Model/Tasks/ITaskManager.cs @@ -9,9 +9,9 @@ namespace MediaBrowser.Model.Tasks { public interface ITaskManager : IDisposable { - event EventHandler<GenericEventArgs<IScheduledTaskWorker>> TaskExecuting; + event EventHandler<GenericEventArgs<IScheduledTaskWorker>>? TaskExecuting; - event EventHandler<TaskCompletionEventArgs> TaskCompleted; + event EventHandler<TaskCompletionEventArgs>? TaskCompleted; /// <summary> /// Gets the list of Scheduled Tasks. diff --git a/MediaBrowser.Model/Updates/VersionInfo.cs b/MediaBrowser.Model/Updates/VersionInfo.cs index 03a540dde..320199f98 100644 --- a/MediaBrowser.Model/Updates/VersionInfo.cs +++ b/MediaBrowser.Model/Updates/VersionInfo.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Model.Updates [JsonPropertyName("version")] public string Version { - get => _version == null ? string.Empty : _version.ToString(); + get => _version is null ? string.Empty : _version.ToString(); set => _version = SysVersion.Parse(value); } diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index 3634d0705..80f5e2c37 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -13,6 +13,7 @@ namespace MediaBrowser.Model.Users public UserPolicy() { IsHidden = true; + EnableCollectionManagement = false; EnableContentDeletion = false; EnableContentDeletionFromFolders = Array.Empty<string>(); @@ -35,6 +36,7 @@ namespace MediaBrowser.Model.Users EnableSharedDeviceControl = true; BlockedTags = Array.Empty<string>(); + AllowedTags = Array.Empty<string>(); BlockUnratedItems = Array.Empty<UnratedItem>(); EnableUserPreferenceAccess = true; @@ -44,6 +46,7 @@ namespace MediaBrowser.Model.Users LoginAttemptsBeforeLockout = -1; MaxActiveSessions = 0; + MaxParentalRating = null; EnableAllChannels = true; EnabledChannels = Array.Empty<Guid>(); @@ -73,6 +76,12 @@ namespace MediaBrowser.Model.Users public bool IsHidden { get; set; } /// <summary> + /// Gets or sets a value indicating whether this instance can manage collections. + /// </summary> + /// <value><c>true</c> if this instance is hidden; otherwise, <c>false</c>.</value> + public bool EnableCollectionManagement { get; set; } + + /// <summary> /// Gets or sets a value indicating whether this instance is disabled. /// </summary> /// <value><c>true</c> if this instance is disabled; otherwise, <c>false</c>.</value> @@ -86,6 +95,8 @@ namespace MediaBrowser.Model.Users public string[] BlockedTags { get; set; } + public string[] AllowedTags { get; set; } + public bool EnableUserPreferenceAccess { get; set; } public AccessSchedule[] AccessSchedules { get; set; } |
