From 7631956451af5927c1c9850eb4e106dc4d0cdde1 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Sat, 7 Sep 2024 18:09:52 -0400 Subject: Backport pull request #12550 from jellyfin/release-10.9.z Create and use FormattingStreamWriter Original-merge: cd2f2ca17800f71c8d94a6e043b49b7c4200e254 Merged-by: Bond-009 Backported-by: Joshua M. Boniface --- src/Jellyfin.Extensions/FormattingStreamWriter.cs | 38 +++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/Jellyfin.Extensions/FormattingStreamWriter.cs (limited to 'src') diff --git a/src/Jellyfin.Extensions/FormattingStreamWriter.cs b/src/Jellyfin.Extensions/FormattingStreamWriter.cs new file mode 100644 index 000000000..40e3c5a68 --- /dev/null +++ b/src/Jellyfin.Extensions/FormattingStreamWriter.cs @@ -0,0 +1,38 @@ +using System; +using System.IO; + +namespace Jellyfin.Extensions; + +/// +/// A custom StreamWriter which supports setting a IFormatProvider. +/// +public class FormattingStreamWriter : StreamWriter +{ + private readonly IFormatProvider _formatProvider; + + /// + /// Initializes a new instance of the class. + /// + /// The stream to write to. + /// The format provider to use. + public FormattingStreamWriter(Stream stream, IFormatProvider formatProvider) + : base(stream) + { + _formatProvider = formatProvider; + } + + /// + /// Initializes a new instance of the class. + /// + /// The complete file path to write to. + /// The format provider to use. + public FormattingStreamWriter(string path, IFormatProvider formatProvider) + : base(path) + { + _formatProvider = formatProvider; + } + + /// + public override IFormatProvider FormatProvider + => _formatProvider; +} -- cgit v1.2.3 From b4f71859d939c5c0fa0a155f712d25cba0c20b8f Mon Sep 17 00:00:00 2001 From: gnattu Date: Sun, 8 Sep 2024 11:08:54 +0800 Subject: Make Live TV compatibility profiles customizable (#12529) --- Jellyfin.Api/Helpers/StreamingHelpers.cs | 25 ++++++---------------- MediaBrowser.Model/Dlna/StreamBuilder.cs | 1 + MediaBrowser.Model/Dto/MediaSourceInfo.cs | 7 ++++++ MediaBrowser.Model/LiveTv/TunerHostInfo.cs | 9 ++++++++ .../TunerHosts/HdHomerun/HdHomerunHost.cs | 2 ++ src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs | 6 ++++-- 6 files changed, 29 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 535ef27c3..3cc6a393b 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -142,28 +142,15 @@ public static class StreamingHelpers } else { - // Enforce more restrictive transcoding profile for LiveTV due to compatability reasons - // Cap the MaxStreamingBitrate to 30Mbps, because we are unable to reliably probe source bitrate, - // which will cause the client to request extremely high bitrate that may fail the player/encoder - streamingRequest.VideoBitRate = streamingRequest.VideoBitRate > 30000000 ? 30000000 : streamingRequest.VideoBitRate; - - if (streamingRequest.SegmentContainer is not null) - { - // Remove all fmp4 transcoding profiles, because it causes playback error and/or A/V sync issues - // Notably: Some channels won't play on FireFox and LG webOS - // Some channels from HDHomerun will experience A/V sync issues - streamingRequest.SegmentContainer = "ts"; - streamingRequest.VideoCodec = "h264"; - streamingRequest.AudioCodec = "aac"; - state.SupportedVideoCodecs = ["h264"]; - state.Request.VideoCodec = "h264"; - state.SupportedAudioCodecs = ["aac"]; - state.Request.AudioCodec = "aac"; - } - var liveStreamInfo = await mediaSourceManager.GetLiveStreamWithDirectStreamProvider(streamingRequest.LiveStreamId, cancellationToken).ConfigureAwait(false); mediaSource = liveStreamInfo.Item1; state.DirectStreamProvider = liveStreamInfo.Item2; + + // Cap the max bitrate when it is too high. This is usually due to ffmpeg is unable to probe the source liveTV streams' bitrate. + if (mediaSource.FallbackMaxStreamingBitrate is not null && streamingRequest.VideoBitRate is not null) + { + streamingRequest.VideoBitRate = Math.Min(streamingRequest.VideoBitRate.Value, mediaSource.FallbackMaxStreamingBitrate.Value); + } } var encodingOptions = serverConfigurationManager.GetEncodingOptions(); diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index ad00149e0..c18becf72 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -805,6 +805,7 @@ namespace MediaBrowser.Model.Dlna } var transcodingProfiles = options.Profile.TranscodingProfiles + .Where(i => !item.UseMostCompatibleTranscodingProfile || string.Equals(i.Container, "ts", StringComparison.OrdinalIgnoreCase)) .Where(i => i.Type == playlistItem.MediaType && i.Context == options.Context); if (options.AllowVideoStreamCopy) diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 1c6037325..eff2e09da 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Text.Json.Serialization; using Jellyfin.Data.Enums; using MediaBrowser.Model.Dlna; @@ -24,6 +25,7 @@ namespace MediaBrowser.Model.Dto SupportsDirectStream = true; SupportsDirectPlay = true; SupportsProbing = true; + UseMostCompatibleTranscodingProfile = false; } public MediaProtocol Protocol { get; set; } @@ -70,6 +72,9 @@ namespace MediaBrowser.Model.Dto public bool IsInfiniteStream { get; set; } + [DefaultValue(false)] + public bool UseMostCompatibleTranscodingProfile { get; set; } + public bool RequiresOpening { get; set; } public string OpenToken { get; set; } @@ -98,6 +103,8 @@ namespace MediaBrowser.Model.Dto public int? Bitrate { get; set; } + public int? FallbackMaxStreamingBitrate { get; set; } + public TransportStreamTimestamp? Timestamp { get; set; } public Dictionary RequiredHttpHeaders { get; set; } diff --git a/MediaBrowser.Model/LiveTv/TunerHostInfo.cs b/MediaBrowser.Model/LiveTv/TunerHostInfo.cs index a832169c2..a355387b1 100644 --- a/MediaBrowser.Model/LiveTv/TunerHostInfo.cs +++ b/MediaBrowser.Model/LiveTv/TunerHostInfo.cs @@ -9,6 +9,9 @@ namespace MediaBrowser.Model.LiveTv { AllowHWTranscoding = true; IgnoreDts = true; + AllowStreamSharing = true; + AllowFmp4TranscodingContainer = false; + FallbackMaxStreamingBitrate = 30000000; } public string Id { get; set; } @@ -25,6 +28,12 @@ namespace MediaBrowser.Model.LiveTv public bool AllowHWTranscoding { get; set; } + public bool AllowFmp4TranscodingContainer { get; set; } + + public bool AllowStreamSharing { get; set; } + + public int FallbackMaxStreamingBitrate { get; set; } + public bool EnableStreamLooping { get; set; } public string Source { get; set; } diff --git a/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index fef84dd00..e1f87a7bd 100644 --- a/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -331,6 +331,8 @@ namespace Jellyfin.LiveTv.TunerHosts.HdHomerun SupportsTranscoding = true, IsInfiniteStream = true, IgnoreDts = true, + UseMostCompatibleTranscodingProfile = true, // All HDHR tuners require this + FallbackMaxStreamingBitrate = info.FallbackMaxStreamingBitrate, // IgnoreIndex = true, // ReadAtNativeFramerate = true }; diff --git a/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs b/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs index 365f0188d..be81171a0 100644 --- a/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs +++ b/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs @@ -94,7 +94,7 @@ namespace Jellyfin.LiveTv.TunerHosts var mediaSource = sources[0]; - if (mediaSource.Protocol == MediaProtocol.Http && !mediaSource.RequiresLooping) + if (tunerHost.AllowStreamSharing && mediaSource.Protocol == MediaProtocol.Http && !mediaSource.RequiresLooping) { var extension = Path.GetExtension(new UriBuilder(mediaSource.Path).Path); @@ -200,7 +200,9 @@ namespace Jellyfin.LiveTv.TunerHosts SupportsDirectPlay = supportsDirectPlay, SupportsDirectStream = supportsDirectStream, - RequiredHttpHeaders = httpHeaders + RequiredHttpHeaders = httpHeaders, + UseMostCompatibleTranscodingProfile = !info.AllowFmp4TranscodingContainer, + FallbackMaxStreamingBitrate = info.FallbackMaxStreamingBitrate }; mediaSource.InferTotalBitrate(); -- cgit v1.2.3 From 0d85af019c6ebc855ab2d8e689abe72b995225b4 Mon Sep 17 00:00:00 2001 From: Tim Eisele Date: Mon, 9 Sep 2024 16:43:37 +0200 Subject: Use enums for encoding options (#12561) --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 4 +- Jellyfin.Api/Controllers/VideosController.cs | 2 +- Jellyfin.Server/Migrations/MigrationRunner.cs | 3 +- .../CreateNetworkConfiguration.cs | 1 - .../PreStartupRoutines/MigrateEncodingOptions.cs | 245 +++++++++ .../MigrateMusicBrainzTimeout.cs | 10 +- .../MigrateNetworkConfiguration.cs | 85 +-- .../MediaEncoding/EncodingHelper.cs | 594 +++++++++------------ MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 17 +- .../Transcoding/TranscodeManager.cs | 7 +- .../Configuration/EncodingOptions.cs | 26 +- MediaBrowser.Model/Entities/DeinterlaceMethod.cs | 19 + MediaBrowser.Model/Entities/EncoderPreset.cs | 64 +++ .../Entities/HardwareAccelerationType.cs | 49 ++ .../Entities/TonemappingAlgorithm.cs | 49 ++ MediaBrowser.Model/Entities/TonemappingMode.cs | 34 ++ MediaBrowser.Model/Entities/TonemappingRange.cs | 24 + MediaBrowser.Model/Session/HardwareEncodingType.cs | 43 -- MediaBrowser.Model/Session/TranscodingInfo.cs | 78 ++- .../Playlist/DynamicHlsPlaylistGenerator.cs | 4 +- 20 files changed, 883 insertions(+), 475 deletions(-) create mode 100644 Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs create mode 100644 MediaBrowser.Model/Entities/DeinterlaceMethod.cs create mode 100644 MediaBrowser.Model/Entities/EncoderPreset.cs create mode 100644 MediaBrowser.Model/Entities/HardwareAccelerationType.cs create mode 100644 MediaBrowser.Model/Entities/TonemappingAlgorithm.cs create mode 100644 MediaBrowser.Model/Entities/TonemappingMode.cs create mode 100644 MediaBrowser.Model/Entities/TonemappingRange.cs delete mode 100644 MediaBrowser.Model/Session/HardwareEncodingType.cs (limited to 'src') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index b9ef189e9..db1d86698 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -40,8 +40,8 @@ namespace Jellyfin.Api.Controllers; [Authorize] public class DynamicHlsController : BaseJellyfinApiController { - private const string DefaultVodEncoderPreset = "veryfast"; - private const string DefaultEventEncoderPreset = "superfast"; + private const EncoderPreset DefaultVodEncoderPreset = EncoderPreset.veryfast; + private const EncoderPreset DefaultEventEncoderPreset = EncoderPreset.superfast; private const TranscodingJobType TranscodingJobType = MediaBrowser.Controller.MediaEncoding.TranscodingJobType.Hls; private readonly Version _minFFmpegFlacInMp4 = new Version(6, 0); diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index effe7b021..8348fd937 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -482,7 +482,7 @@ public class VideosController : BaseJellyfinApiController // Need to start ffmpeg (because media can't be returned directly) var encodingOptions = _serverConfigurationManager.GetEncodingOptions(); - var ffmpegCommandLineArguments = _encodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, "superfast"); + var ffmpegCommandLineArguments = _encodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, EncoderPreset.superfast); return await FileStreamResponseHelpers.GetTranscodedFile( state, isHeadRequest, diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 8682f28e0..9d4441ac3 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -23,7 +23,8 @@ namespace Jellyfin.Server.Migrations { typeof(PreStartupRoutines.CreateNetworkConfiguration), typeof(PreStartupRoutines.MigrateMusicBrainzTimeout), - typeof(PreStartupRoutines.MigrateNetworkConfiguration) + typeof(PreStartupRoutines.MigrateNetworkConfiguration), + typeof(PreStartupRoutines.MigrateEncodingOptions) }; /// diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs index 139a6ec64..8462d0a8c 100644 --- a/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs @@ -132,5 +132,4 @@ public class CreateNetworkConfiguration : IMigrationRoutine public string[] KnownProxies { get; set; } = Array.Empty(); } -#pragma warning restore } diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs new file mode 100644 index 000000000..61f5620dc --- /dev/null +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs @@ -0,0 +1,245 @@ +using System; +using System.IO; +using System.Xml; +using System.Xml.Serialization; +using Emby.Server.Implementations; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Entities; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.PreStartupRoutines; + +/// +public class MigrateEncodingOptions : IMigrationRoutine +{ + private readonly ServerApplicationPaths _applicationPaths; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// An instance of . + /// An instance of the interface. + public MigrateEncodingOptions(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory) + { + _applicationPaths = applicationPaths; + _logger = loggerFactory.CreateLogger(); + } + + /// + public Guid Id => Guid.Parse("A8E61960-7726-4450-8F3D-82C12DAABBCB"); + + /// + public string Name => nameof(MigrateEncodingOptions); + + /// + public bool PerformOnNewInstall => false; + + /// + public void Perform() + { + string path = Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "encoding.xml"); + var oldSerializer = new XmlSerializer(typeof(OldEncodingOptions), new XmlRootAttribute("EncodingOptions")); + OldEncodingOptions? oldConfig = null; + + try + { + using var xmlReader = XmlReader.Create(path); + oldConfig = (OldEncodingOptions?)oldSerializer.Deserialize(xmlReader); + } + catch (InvalidOperationException ex) + { + _logger.LogError(ex, "Migrate EncodingOptions deserialize Invalid Operation error"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Migrate EncodingOptions deserialize error"); + } + + if (oldConfig is null) + { + return; + } + + var hardwareAccelerationType = HardwareAccelerationType.none; + if (Enum.TryParse(oldConfig.HardwareAccelerationType, true, out var parsedHardwareAccelerationType)) + { + hardwareAccelerationType = parsedHardwareAccelerationType; + } + + var tonemappingAlgorithm = TonemappingAlgorithm.none; + if (Enum.TryParse(oldConfig.TonemappingAlgorithm, true, out var parsedTonemappingAlgorithm)) + { + tonemappingAlgorithm = parsedTonemappingAlgorithm; + } + + var tonemappingMode = TonemappingMode.auto; + if (Enum.TryParse(oldConfig.TonemappingMode, true, out var parsedTonemappingMode)) + { + tonemappingMode = parsedTonemappingMode; + } + + var tonemappingRange = TonemappingRange.auto; + if (Enum.TryParse(oldConfig.TonemappingRange, true, out var parsedTonemappingRange)) + { + tonemappingRange = parsedTonemappingRange; + } + + var encoderPreset = EncoderPreset.superfast; + if (Enum.TryParse(oldConfig.TonemappingRange, true, out var parsedEncoderPreset)) + { + encoderPreset = parsedEncoderPreset; + } + + var deinterlaceMethod = DeinterlaceMethod.yadif; + if (Enum.TryParse(oldConfig.TonemappingRange, true, out var parsedDeinterlaceMethod)) + { + deinterlaceMethod = parsedDeinterlaceMethod; + } + + var encodingOptions = new EncodingOptions() + { + EncodingThreadCount = oldConfig.EncodingThreadCount, + TranscodingTempPath = oldConfig.TranscodingTempPath, + FallbackFontPath = oldConfig.FallbackFontPath, + EnableFallbackFont = oldConfig.EnableFallbackFont, + EnableAudioVbr = oldConfig.EnableAudioVbr, + DownMixAudioBoost = oldConfig.DownMixAudioBoost, + DownMixStereoAlgorithm = oldConfig.DownMixStereoAlgorithm, + MaxMuxingQueueSize = oldConfig.MaxMuxingQueueSize, + EnableThrottling = oldConfig.EnableThrottling, + ThrottleDelaySeconds = oldConfig.ThrottleDelaySeconds, + EnableSegmentDeletion = oldConfig.EnableSegmentDeletion, + SegmentKeepSeconds = oldConfig.SegmentKeepSeconds, + HardwareAccelerationType = hardwareAccelerationType, + EncoderAppPath = oldConfig.EncoderAppPath, + EncoderAppPathDisplay = oldConfig.EncoderAppPathDisplay, + VaapiDevice = oldConfig.VaapiDevice, + EnableTonemapping = oldConfig.EnableTonemapping, + EnableVppTonemapping = oldConfig.EnableVppTonemapping, + EnableVideoToolboxTonemapping = oldConfig.EnableVideoToolboxTonemapping, + TonemappingAlgorithm = tonemappingAlgorithm, + TonemappingMode = tonemappingMode, + TonemappingRange = tonemappingRange, + TonemappingDesat = oldConfig.TonemappingDesat, + TonemappingPeak = oldConfig.TonemappingPeak, + TonemappingParam = oldConfig.TonemappingParam, + VppTonemappingBrightness = oldConfig.VppTonemappingBrightness, + VppTonemappingContrast = oldConfig.VppTonemappingContrast, + H264Crf = oldConfig.H264Crf, + H265Crf = oldConfig.H265Crf, + EncoderPreset = encoderPreset, + DeinterlaceDoubleRate = oldConfig.DeinterlaceDoubleRate, + DeinterlaceMethod = deinterlaceMethod, + EnableDecodingColorDepth10Hevc = oldConfig.EnableDecodingColorDepth10Hevc, + EnableDecodingColorDepth10Vp9 = oldConfig.EnableDecodingColorDepth10Vp9, + EnableEnhancedNvdecDecoder = oldConfig.EnableEnhancedNvdecDecoder, + PreferSystemNativeHwDecoder = oldConfig.PreferSystemNativeHwDecoder, + EnableIntelLowPowerH264HwEncoder = oldConfig.EnableIntelLowPowerH264HwEncoder, + EnableIntelLowPowerHevcHwEncoder = oldConfig.EnableIntelLowPowerHevcHwEncoder, + EnableHardwareEncoding = oldConfig.EnableHardwareEncoding, + AllowHevcEncoding = oldConfig.AllowHevcEncoding, + AllowAv1Encoding = oldConfig.AllowAv1Encoding, + EnableSubtitleExtraction = oldConfig.EnableSubtitleExtraction, + HardwareDecodingCodecs = oldConfig.HardwareDecodingCodecs, + AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = oldConfig.AllowOnDemandMetadataBasedKeyframeExtractionForExtensions + }; + + var newSerializer = new XmlSerializer(typeof(EncodingOptions)); + var xmlWriterSettings = new XmlWriterSettings { Indent = true }; + using var xmlWriter = XmlWriter.Create(path, xmlWriterSettings); + newSerializer.Serialize(xmlWriter, encodingOptions); + } + +#pragma warning disable + public sealed class OldEncodingOptions + { + public int EncodingThreadCount { get; set; } + + public string TranscodingTempPath { get; set; } + + public string FallbackFontPath { get; set; } + + public bool EnableFallbackFont { get; set; } + + public bool EnableAudioVbr { get; set; } + + public double DownMixAudioBoost { get; set; } + + public DownMixStereoAlgorithms DownMixStereoAlgorithm { get; set; } + + public int MaxMuxingQueueSize { get; set; } + + public bool EnableThrottling { get; set; } + + public int ThrottleDelaySeconds { get; set; } + + public bool EnableSegmentDeletion { get; set; } + + public int SegmentKeepSeconds { get; set; } + + public string HardwareAccelerationType { get; set; } + + public string EncoderAppPath { get; set; } + + public string EncoderAppPathDisplay { get; set; } + + public string VaapiDevice { get; set; } + + public bool EnableTonemapping { get; set; } + + public bool EnableVppTonemapping { get; set; } + + public bool EnableVideoToolboxTonemapping { get; set; } + + public string TonemappingAlgorithm { get; set; } + + public string TonemappingMode { get; set; } + + public string TonemappingRange { get; set; } + + public double TonemappingDesat { 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 AllowAv1Encoding { get; set; } + + public bool EnableSubtitleExtraction { get; set; } + + public string[] HardwareDecodingCodecs { get; set; } + + public string[] AllowOnDemandMetadataBasedKeyframeExtractionForExtensions { get; set; } + } +} diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs index 0544fe561..580282a5f 100644 --- a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs @@ -48,9 +48,11 @@ public class MigrateMusicBrainzTimeout : IMigrationRoutine if (oldPluginConfiguration is not null) { - var newPluginConfiguration = new PluginConfiguration(); - newPluginConfiguration.Server = oldPluginConfiguration.Server; - newPluginConfiguration.ReplaceArtistName = oldPluginConfiguration.ReplaceArtistName; + var newPluginConfiguration = new PluginConfiguration + { + Server = oldPluginConfiguration.Server, + ReplaceArtistName = oldPluginConfiguration.ReplaceArtistName + }; var newRateLimit = oldPluginConfiguration.RateLimit / 1000.0; newPluginConfiguration.RateLimit = newRateLimit < 1.0 ? 1.0 : newRateLimit; WriteNew(path, newPluginConfiguration); @@ -93,6 +95,4 @@ public class MigrateMusicBrainzTimeout : IMigrationRoutine public bool ReplaceArtistName { get; set; } } -#pragma warning restore - } diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateNetworkConfiguration.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateNetworkConfiguration.cs index d92c00991..49960f430 100644 --- a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateNetworkConfiguration.cs +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateNetworkConfiguration.cs @@ -55,49 +55,53 @@ public class MigrateNetworkConfiguration : IMigrationRoutine _logger.LogError(ex, "Migrate NetworkConfiguration deserialize error"); } - if (oldNetworkConfiguration is not null) + if (oldNetworkConfiguration is null) { - // Migrate network config values to new config schema - var networkConfiguration = new NetworkConfiguration(); - networkConfiguration.AutoDiscovery = oldNetworkConfiguration.AutoDiscovery; - networkConfiguration.BaseUrl = oldNetworkConfiguration.BaseUrl; - networkConfiguration.CertificatePassword = oldNetworkConfiguration.CertificatePassword; - networkConfiguration.CertificatePath = oldNetworkConfiguration.CertificatePath; - networkConfiguration.EnableHttps = oldNetworkConfiguration.EnableHttps; - networkConfiguration.EnableIPv4 = oldNetworkConfiguration.EnableIPV4; - networkConfiguration.EnableIPv6 = oldNetworkConfiguration.EnableIPV6; - networkConfiguration.EnablePublishedServerUriByRequest = oldNetworkConfiguration.EnablePublishedServerUriByRequest; - networkConfiguration.EnableRemoteAccess = oldNetworkConfiguration.EnableRemoteAccess; - networkConfiguration.EnableUPnP = oldNetworkConfiguration.EnableUPnP; - networkConfiguration.IgnoreVirtualInterfaces = oldNetworkConfiguration.IgnoreVirtualInterfaces; - networkConfiguration.InternalHttpPort = oldNetworkConfiguration.HttpServerPortNumber; - networkConfiguration.InternalHttpsPort = oldNetworkConfiguration.HttpsPortNumber; - networkConfiguration.IsRemoteIPFilterBlacklist = oldNetworkConfiguration.IsRemoteIPFilterBlacklist; - networkConfiguration.KnownProxies = oldNetworkConfiguration.KnownProxies; - networkConfiguration.LocalNetworkAddresses = oldNetworkConfiguration.LocalNetworkAddresses; - networkConfiguration.LocalNetworkSubnets = oldNetworkConfiguration.LocalNetworkSubnets; - networkConfiguration.PublicHttpPort = oldNetworkConfiguration.PublicPort; - networkConfiguration.PublicHttpsPort = oldNetworkConfiguration.PublicHttpsPort; - networkConfiguration.PublishedServerUriBySubnet = oldNetworkConfiguration.PublishedServerUriBySubnet; - networkConfiguration.RemoteIPFilter = oldNetworkConfiguration.RemoteIPFilter; - networkConfiguration.RequireHttps = oldNetworkConfiguration.RequireHttps; - - // Migrate old virtual interface name schema - var oldVirtualInterfaceNames = oldNetworkConfiguration.VirtualInterfaceNames; - if (oldVirtualInterfaceNames.Equals("vEthernet*", StringComparison.OrdinalIgnoreCase)) - { - networkConfiguration.VirtualInterfaceNames = new string[] { "veth" }; - } - else - { - networkConfiguration.VirtualInterfaceNames = oldVirtualInterfaceNames.Replace("*", string.Empty, StringComparison.OrdinalIgnoreCase).Split(','); - } + return; + } - var networkConfigSerializer = new XmlSerializer(typeof(NetworkConfiguration)); - var xmlWriterSettings = new XmlWriterSettings { Indent = true }; - using var xmlWriter = XmlWriter.Create(path, xmlWriterSettings); - networkConfigSerializer.Serialize(xmlWriter, networkConfiguration); + // Migrate network config values to new config schema + var networkConfiguration = new NetworkConfiguration + { + AutoDiscovery = oldNetworkConfiguration.AutoDiscovery, + BaseUrl = oldNetworkConfiguration.BaseUrl, + CertificatePassword = oldNetworkConfiguration.CertificatePassword, + CertificatePath = oldNetworkConfiguration.CertificatePath, + EnableHttps = oldNetworkConfiguration.EnableHttps, + EnableIPv4 = oldNetworkConfiguration.EnableIPV4, + EnableIPv6 = oldNetworkConfiguration.EnableIPV6, + EnablePublishedServerUriByRequest = oldNetworkConfiguration.EnablePublishedServerUriByRequest, + EnableRemoteAccess = oldNetworkConfiguration.EnableRemoteAccess, + EnableUPnP = oldNetworkConfiguration.EnableUPnP, + IgnoreVirtualInterfaces = oldNetworkConfiguration.IgnoreVirtualInterfaces, + InternalHttpPort = oldNetworkConfiguration.HttpServerPortNumber, + InternalHttpsPort = oldNetworkConfiguration.HttpsPortNumber, + IsRemoteIPFilterBlacklist = oldNetworkConfiguration.IsRemoteIPFilterBlacklist, + KnownProxies = oldNetworkConfiguration.KnownProxies, + LocalNetworkAddresses = oldNetworkConfiguration.LocalNetworkAddresses, + LocalNetworkSubnets = oldNetworkConfiguration.LocalNetworkSubnets, + PublicHttpPort = oldNetworkConfiguration.PublicPort, + PublicHttpsPort = oldNetworkConfiguration.PublicHttpsPort, + PublishedServerUriBySubnet = oldNetworkConfiguration.PublishedServerUriBySubnet, + RemoteIPFilter = oldNetworkConfiguration.RemoteIPFilter, + RequireHttps = oldNetworkConfiguration.RequireHttps + }; + + // Migrate old virtual interface name schema + var oldVirtualInterfaceNames = oldNetworkConfiguration.VirtualInterfaceNames; + if (oldVirtualInterfaceNames.Equals("vEthernet*", StringComparison.OrdinalIgnoreCase)) + { + networkConfiguration.VirtualInterfaceNames = new string[] { "veth" }; } + else + { + networkConfiguration.VirtualInterfaceNames = oldVirtualInterfaceNames.Replace("*", string.Empty, StringComparison.OrdinalIgnoreCase).Split(','); + } + + var networkConfigSerializer = new XmlSerializer(typeof(NetworkConfiguration)); + var xmlWriterSettings = new XmlWriterSettings { Indent = true }; + using var xmlWriter = XmlWriter.Create(path, xmlWriterSettings); + networkConfigSerializer.Serialize(xmlWriter, networkConfiguration); } #pragma warning disable @@ -204,5 +208,4 @@ public class MigrateNetworkConfiguration : IMigrationRoutine public bool EnablePublishedServerUriByRequest { get; set; } = false; } -#pragma warning restore } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index d6ad7e2b3..fdc56652a 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -37,6 +37,8 @@ namespace MediaBrowser.Controller.MediaEncoding /// public const string ValidationRegex = @"^[a-zA-Z0-9\-\._,|]{0,40}$"; + private const string _defaultMjpegEncoder = "mjpeg"; + private const string QsvAlias = "qs"; private const string VaapiAlias = "va"; private const string D3d11vaAlias = "dx11"; @@ -72,8 +74,8 @@ namespace MediaBrowser.Controller.MediaEncoding private static readonly Regex _validationRegex = new(ValidationRegex, RegexOptions.Compiled); - private static readonly string[] _videoProfilesH264 = new[] - { + private static readonly string[] _videoProfilesH264 = + [ "ConstrainedBaseline", "Baseline", "Extended", @@ -82,20 +84,20 @@ namespace MediaBrowser.Controller.MediaEncoding "ProgressiveHigh", "ConstrainedHigh", "High10" - }; + ]; - private static readonly string[] _videoProfilesH265 = new[] - { + private static readonly string[] _videoProfilesH265 = + [ "Main", "Main10" - }; + ]; - private static readonly string[] _videoProfilesAv1 = new[] - { + private static readonly string[] _videoProfilesAv1 = + [ "Main", "High", "Professional", - }; + ]; private static readonly HashSet _mp4ContainerNames = new(StringComparer.OrdinalIgnoreCase) { @@ -107,8 +109,8 @@ namespace MediaBrowser.Controller.MediaEncoding "m4v", }; - private static readonly string[] _legacyTonemapModes = new[] { "max", "rgb" }; - private static readonly string[] _advancedTonemapModes = new[] { "lum", "itp" }; + private static readonly TonemappingMode[] _legacyTonemapModes = [TonemappingMode.max, TonemappingMode.rgb]; + private static readonly TonemappingMode[] _advancedTonemapModes = [TonemappingMode.lum, TonemappingMode.itp]; // Set max transcoding channels for encoders that can't handle more than a set amount of channels // AAC, FLAC, ALAC, libopus, libvorbis encoders all support at least 8 channels @@ -123,23 +125,22 @@ namespace MediaBrowser.Controller.MediaEncoding { "truehd", 6 }, }; - private static readonly string _defaultMjpegEncoder = "mjpeg"; - private static readonly Dictionary _mjpegCodecMap = new(StringComparer.OrdinalIgnoreCase) + private static readonly Dictionary _mjpegCodecMap = new() { - { "vaapi", _defaultMjpegEncoder + "_vaapi" }, - { "qsv", _defaultMjpegEncoder + "_qsv" }, - { "videotoolbox", _defaultMjpegEncoder + "_videotoolbox" } + { HardwareAccelerationType.vaapi, _defaultMjpegEncoder + "_vaapi" }, + { HardwareAccelerationType.qsv, _defaultMjpegEncoder + "_qsv" }, + { HardwareAccelerationType.videotoolbox, _defaultMjpegEncoder + "_videotoolbox" } }; - public static readonly string[] LosslessAudioCodecs = new string[] - { + public static readonly string[] LosslessAudioCodecs = + [ "alac", "ape", "flac", "mlp", "truehd", "wavpack" - }; + ]; public EncodingHelper( IApplicationPaths appPaths, @@ -176,18 +177,18 @@ namespace MediaBrowser.Controller.MediaEncoding { var hwType = encodingOptions.HardwareAccelerationType; - var codecMap = new Dictionary(StringComparer.OrdinalIgnoreCase) + var codecMap = new Dictionary() { - { "amf", hwEncoder + "_amf" }, - { "nvenc", hwEncoder + "_nvenc" }, - { "qsv", hwEncoder + "_qsv" }, - { "vaapi", hwEncoder + "_vaapi" }, - { "videotoolbox", hwEncoder + "_videotoolbox" }, - { "v4l2m2m", hwEncoder + "_v4l2m2m" }, - { "rkmpp", hwEncoder + "_rkmpp" }, + { HardwareAccelerationType.amf, hwEncoder + "_amf" }, + { HardwareAccelerationType.nvenc, hwEncoder + "_nvenc" }, + { HardwareAccelerationType.qsv, hwEncoder + "_qsv" }, + { HardwareAccelerationType.vaapi, hwEncoder + "_vaapi" }, + { HardwareAccelerationType.videotoolbox, hwEncoder + "_videotoolbox" }, + { HardwareAccelerationType.v4l2m2m, hwEncoder + "_v4l2m2m" }, + { HardwareAccelerationType.rkmpp, hwEncoder + "_rkmpp" }, }; - if (!string.IsNullOrEmpty(hwType) + if (hwType != HardwareAccelerationType.none && encodingOptions.EnableHardwareEncoding && codecMap.TryGetValue(hwType, out var preferredEncoder) && _mediaEncoder.SupportsEncoder(preferredEncoder)) @@ -205,7 +206,7 @@ namespace MediaBrowser.Controller.MediaEncoding { var hwType = encodingOptions.HardwareAccelerationType; - if (!string.IsNullOrEmpty(hwType) + if (hwType != HardwareAccelerationType.none && encodingOptions.EnableHardwareEncoding && _mjpegCodecMap.TryGetValue(hwType, out var preferredEncoder) && _mediaEncoder.SupportsEncoder(preferredEncoder)) @@ -360,7 +361,7 @@ namespace MediaBrowser.Controller.MediaEncoding // prefer 'tonemap_vaapi' over 'vpp_qsv' on Linux for supporting Gen9/KBLx. // 'vpp_qsv' requires VPL, which is only supported on Gen12/TGLx and newer. if (OperatingSystem.IsWindows() - && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) + && options.HardwareAccelerationType == HardwareAccelerationType.qsv && _mediaEncoder.EncoderVersion < _minFFmpegQsvVppTonemapOption) { return false; @@ -970,7 +971,7 @@ namespace MediaBrowser.Controller.MediaEncoding var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty; var isHwTonemapAvailable = IsHwTonemapAvailable(state, options); - if (string.Equals(optHwaccelType, "vaapi", StringComparison.OrdinalIgnoreCase)) + if (optHwaccelType == HardwareAccelerationType.vaapi) { if (!isLinux || !_mediaEncoder.SupportsHwaccel("vaapi")) { @@ -1044,7 +1045,7 @@ namespace MediaBrowser.Controller.MediaEncoding args.Append(filterDevArgs); } - else if (string.Equals(optHwaccelType, "qsv", StringComparison.OrdinalIgnoreCase)) + else if (optHwaccelType == HardwareAccelerationType.qsv) { if ((!isLinux && !isWindows) || !_mediaEncoder.SupportsHwaccel("qsv")) { @@ -1079,7 +1080,7 @@ namespace MediaBrowser.Controller.MediaEncoding args.Append(filterDevArgs); } - else if (string.Equals(optHwaccelType, "nvenc", StringComparison.OrdinalIgnoreCase)) + else if (optHwaccelType == HardwareAccelerationType.nvenc) { if ((!isLinux && !isWindows) || !IsCudaFullSupported()) { @@ -1098,7 +1099,7 @@ namespace MediaBrowser.Controller.MediaEncoding args.Append(GetCudaDeviceArgs(0, CudaAlias)) .Append(GetFilterHwDeviceArgs(CudaAlias)); } - else if (string.Equals(optHwaccelType, "amf", StringComparison.OrdinalIgnoreCase)) + else if (optHwaccelType == HardwareAccelerationType.amf) { if (!isWindows || !_mediaEncoder.SupportsHwaccel("d3d11va")) { @@ -1123,7 +1124,7 @@ namespace MediaBrowser.Controller.MediaEncoding args.Append(filterDevArgs); } - else if (string.Equals(optHwaccelType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) + else if (optHwaccelType == HardwareAccelerationType.videotoolbox) { if (!isMacOS || !_mediaEncoder.SupportsHwaccel("videotoolbox")) { @@ -1140,7 +1141,7 @@ namespace MediaBrowser.Controller.MediaEncoding // videotoolbox hw filter does not require device selection args.Append(GetVideoToolboxDeviceArgs(VideotoolboxAlias)); } - else if (string.Equals(optHwaccelType, "rkmpp", StringComparison.OrdinalIgnoreCase)) + else if (optHwaccelType == HardwareAccelerationType.rkmpp) { if (!isLinux || !_mediaEncoder.SupportsHwaccel("rkmpp")) { @@ -1413,6 +1414,149 @@ namespace MediaBrowser.Controller.MediaEncoding return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"); } + private string GetEncoderParam(EncoderPreset? preset, EncoderPreset defaultPreset, EncodingOptions encodingOptions, string videoEncoder, bool isLibX265) + { + var param = string.Empty; + var encoderPreset = preset ?? defaultPreset; + if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265) + { + param += " -preset " + encoderPreset.ToString().ToLowerInvariant(); + + int encodeCrf = encodingOptions.H264Crf; + if (isLibX265) + { + encodeCrf = encodingOptions.H265Crf; + } + + if (encodeCrf >= 0 && encodeCrf <= 51) + { + param += " -crf " + encodeCrf.ToString(CultureInfo.InvariantCulture); + } + else + { + string defaultCrf = "23"; + if (isLibX265) + { + defaultCrf = "28"; + } + + param += " -crf " + defaultCrf; + } + } + else if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase)) + { + // Default to use the recommended preset 10. + // Omit presets < 5, which are too slow for on the fly encoding. + // https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md + param += encoderPreset switch + { + EncoderPreset.veryslow => " -preset 5", + EncoderPreset.slower => " -preset 6", + EncoderPreset.slow => " -preset 7", + EncoderPreset.medium => " -preset 8", + EncoderPreset.fast => " -preset 9", + EncoderPreset.faster => " -preset 10", + EncoderPreset.veryfast => " -preset 11", + EncoderPreset.superfast => " -preset 12", + EncoderPreset.ultrafast => " -preset 13", + _ => " -preset 10" + }; + } + else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase)) + { + // -compression_level is not reliable on AMD. + if (_mediaEncoder.IsVaapiDeviceInteliHD) + { + param += encoderPreset switch + { + EncoderPreset.veryslow => " -compression_level 1", + EncoderPreset.slower => " -compression_level 2", + EncoderPreset.slow => " -compression_level 3", + EncoderPreset.medium => " -compression_level 4", + EncoderPreset.fast => " -compression_level 5", + EncoderPreset.faster => " -compression_level 6", + EncoderPreset.veryfast => " -compression_level 7", + EncoderPreset.superfast => " -compression_level 7", + EncoderPreset.ultrafast => " -compression_level 7", + _ => string.Empty + }; + } + } + else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv) + || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) // hevc (hevc_qsv) + || string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)) // av1 (av1_qsv) + { + EncoderPreset[] valid_presets = [EncoderPreset.veryslow, EncoderPreset.slower, EncoderPreset.slow, EncoderPreset.medium, EncoderPreset.fast, EncoderPreset.faster, EncoderPreset.veryfast]; + + if (valid_presets.Contains(encoderPreset)) + { + param += " -preset " + encodingOptions.EncoderPreset; + } + else + { + param += " -preset " + EncoderPreset.veryfast.ToString().ToLowerInvariant(); + } + } + else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc) + || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) // hevc (hevc_nvenc) + || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase) // av1 (av1_nvenc) + ) + { + param += encoderPreset switch + { + EncoderPreset.veryslow => " -preset p7", + EncoderPreset.slower => " -preset p6", + EncoderPreset.slow => " -preset p5", + EncoderPreset.medium => " -preset p4", + EncoderPreset.fast => " -preset p3", + EncoderPreset.faster => " -preset p2", + _ => " -preset p1" + }; + } + else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf) + || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) // hevc (hevc_amf) + || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase) // av1 (av1_amf) + ) + { + param += encoderPreset switch + { + EncoderPreset.veryslow => " -quality quality", + EncoderPreset.slower => " -quality quality", + EncoderPreset.slow => " -quality quality", + EncoderPreset.medium => " -quality balanced", + _ => " -quality speed" + }; + + if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase)) + { + param += " -header_insertion_mode gop"; + } + + if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) + { + param += " -gops_per_idr 1"; + } + } + else if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase) // h264 (h264_videotoolbox) + || string.Equals(videoEncoder, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase) // hevc (hevc_videotoolbox) + ) + { + param += encoderPreset switch + { + EncoderPreset.veryslow => " -prio_speed 0", + EncoderPreset.slower => " -prio_speed 0", + EncoderPreset.slow => " -prio_speed 0", + EncoderPreset.medium => " -prio_speed 0", + _ => " -prio_speed 1" + }; + } + + return param; + } + public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level) { if (double.TryParse(level, CultureInfo.InvariantCulture, out double requestLevel)) @@ -1625,7 +1769,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// Encoding options. /// Default present to use for encoding. /// Video bitrate. - public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, string defaultPreset) + public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, EncoderPreset defaultPreset) { var param = string.Empty; @@ -1640,7 +1784,9 @@ namespace MediaBrowser.Controller.MediaEncoding // https://github.com/intel/media-driver/issues/1456 var enableWaFori915Hang = false; - if (string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + var hardwareAccelerationType = encodingOptions.HardwareAccelerationType; + + if (hardwareAccelerationType == HardwareAccelerationType.vaapi) { var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965; @@ -1653,7 +1799,7 @@ namespace MediaBrowser.Controller.MediaEncoding intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder && isIntelVaapiDriver; } } - else if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + else if (hardwareAccelerationType == HardwareAccelerationType.qsv) { if (OperatingSystem.IsLinux()) { @@ -1700,204 +1846,10 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -async_depth 1"; } - var isVc1 = string.Equals(state.VideoStream?.Codec, "vc1", StringComparison.OrdinalIgnoreCase); var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase); + var encodingPreset = encodingOptions.EncoderPreset; - if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265) - { - if (!string.IsNullOrEmpty(encodingOptions.EncoderPreset)) - { - param += " -preset " + encodingOptions.EncoderPreset; - } - else - { - param += " -preset " + defaultPreset; - } - - int encodeCrf = encodingOptions.H264Crf; - if (isLibX265) - { - encodeCrf = encodingOptions.H265Crf; - } - - if (encodeCrf >= 0 && encodeCrf <= 51) - { - param += " -crf " + encodeCrf.ToString(CultureInfo.InvariantCulture); - } - else - { - string defaultCrf = "23"; - if (isLibX265) - { - defaultCrf = "28"; - } - - param += " -crf " + defaultCrf; - } - } - else if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase)) - { - // Default to use the recommended preset 10. - // Omit presets < 5, which are too slow for on the fly encoding. - // https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md - param += encodingOptions.EncoderPreset switch - { - "veryslow" => " -preset 5", - "slower" => " -preset 6", - "slow" => " -preset 7", - "medium" => " -preset 8", - "fast" => " -preset 9", - "faster" => " -preset 10", - "veryfast" => " -preset 11", - "superfast" => " -preset 12", - "ultrafast" => " -preset 13", - _ => " -preset 10" - }; - } - else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase)) - { - // -compression_level is not reliable on AMD. - if (_mediaEncoder.IsVaapiDeviceInteliHD) - { - param += encodingOptions.EncoderPreset switch - { - "veryslow" => " -compression_level 1", - "slower" => " -compression_level 2", - "slow" => " -compression_level 3", - "medium" => " -compression_level 4", - "fast" => " -compression_level 5", - "faster" => " -compression_level 6", - "veryfast" => " -compression_level 7", - "superfast" => " -compression_level 7", - "ultrafast" => " -compression_level 7", - _ => string.Empty - }; - } - } - else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv) - || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) // hevc (hevc_qsv) - || string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)) // av1 (av1_qsv) - { - string[] valid_presets = { "veryslow", "slower", "slow", "medium", "fast", "faster", "veryfast" }; - - if (valid_presets.Contains(encodingOptions.EncoderPreset, StringComparison.OrdinalIgnoreCase)) - { - param += " -preset " + encodingOptions.EncoderPreset; - } - else - { - param += " -preset veryfast"; - } - } - else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc) - || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) // hevc (hevc_nvenc) - || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)) // av1 (av1_nvenc) - { - switch (encodingOptions.EncoderPreset) - { - case "veryslow": - param += " -preset p7"; - break; - - case "slower": - param += " -preset p6"; - break; - - case "slow": - param += " -preset p5"; - break; - - case "medium": - param += " -preset p4"; - break; - - case "fast": - param += " -preset p3"; - break; - - case "faster": - param += " -preset p2"; - break; - - case "veryfast": - case "superfast": - case "ultrafast": - param += " -preset p1"; - break; - - default: - param += " -preset p1"; - break; - } - } - else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf) - || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) // hevc (hevc_amf) - || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase)) // av1 (av1_amf) - { - switch (encodingOptions.EncoderPreset) - { - case "veryslow": - case "slower": - case "slow": - param += " -quality quality"; - break; - - case "medium": - param += " -quality balanced"; - break; - - case "fast": - case "faster": - case "veryfast": - case "superfast": - case "ultrafast": - param += " -quality speed"; - break; - - default: - param += " -quality speed"; - break; - } - - if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase)) - { - param += " -header_insertion_mode gop"; - } - - if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) - { - param += " -gops_per_idr 1"; - } - } - else if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase) // h264 (h264_videotoolbox) - || string.Equals(videoEncoder, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_videotoolbox) - { - switch (encodingOptions.EncoderPreset) - { - case "veryslow": - case "slower": - case "slow": - case "medium": - param += " -prio_speed 0"; - break; - - case "fast": - case "faster": - case "veryfast": - case "superfast": - case "ultrafast": - param += " -prio_speed 1"; - break; - - default: - param += " -prio_speed 1"; - break; - } - } - + param += GetEncoderParam(encodingPreset, defaultPreset, encodingOptions, videoEncoder, isLibX265); param += GetVideoBitrateParam(state, videoEncoder); var framerate = GetFramerateParam(state); @@ -3256,7 +3208,7 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Format( CultureInfo.InvariantCulture, "{0}={1}:-1:0", - string.Equals(options.DeinterlaceMethod, "bwdif", StringComparison.OrdinalIgnoreCase) ? "bwdif" : "yadif", + options.DeinterlaceMethod.ToString().ToLowerInvariant(), doubleRateDeint ? "1" : "0"); } @@ -3265,8 +3217,7 @@ namespace MediaBrowser.Controller.MediaEncoding var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30; if (hwDeintSuffix.Contains("cuda", StringComparison.OrdinalIgnoreCase)) { - var useBwdif = string.Equals(options.DeinterlaceMethod, "bwdif", StringComparison.OrdinalIgnoreCase) - && _mediaEncoder.SupportsFilter("bwdif_cuda"); + var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwdif_cuda"); return string.Format( CultureInfo.InvariantCulture, @@ -3307,7 +3258,10 @@ namespace MediaBrowser.Controller.MediaEncoding } var args = string.Empty; - var algorithm = options.TonemappingAlgorithm; + var algorithm = options.TonemappingAlgorithm.ToString().ToLowerInvariant(); + var mode = options.TonemappingMode.ToString().ToLowerInvariant(); + var range = options.TonemappingRange; + var rangeString = range.ToString().ToLowerInvariant(); if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase)) { @@ -3342,10 +3296,10 @@ namespace MediaBrowser.Controller.MediaEncoding args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}"; var useLegacyTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode - && _legacyTonemapModes.Contains(options.TonemappingMode, StringComparison.OrdinalIgnoreCase); + && _legacyTonemapModes.Contains(options.TonemappingMode); var useAdvancedTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegAdvancedTonemapMode - && _advancedTonemapModes.Contains(options.TonemappingMode, StringComparison.OrdinalIgnoreCase); + && _advancedTonemapModes.Contains(options.TonemappingMode); if (useLegacyTonemapModes || useAdvancedTonemapModes) { @@ -3357,8 +3311,7 @@ namespace MediaBrowser.Controller.MediaEncoding args += ":param={6}"; } - if (string.Equals(options.TonemappingRange, "tv", StringComparison.OrdinalIgnoreCase) - || string.Equals(options.TonemappingRange, "pc", StringComparison.OrdinalIgnoreCase)) + if (range == TonemappingRange.tv || range == TonemappingRange.pc) { args += ":range={7}"; } @@ -3372,9 +3325,9 @@ namespace MediaBrowser.Controller.MediaEncoding algorithm, options.TonemappingPeak, options.TonemappingDesat, - options.TonemappingMode, + mode, options.TonemappingParam, - options.TonemappingRange); + rangeString); } public string GetLibplaceboFilter( @@ -3409,24 +3362,24 @@ namespace MediaBrowser.Controller.MediaEncoding if (doTonemap) { var algorithm = options.TonemappingAlgorithm; + var algorithmString = "clip"; var mode = options.TonemappingMode; var range = options.TonemappingRange; - if (string.Equals(algorithm, "bt2390", StringComparison.OrdinalIgnoreCase)) + if (algorithm == TonemappingAlgorithm.bt2390) { - algorithm = "bt.2390"; + algorithmString = "bt.2390"; } - else if (string.Equals(algorithm, "none", StringComparison.OrdinalIgnoreCase)) + else if (algorithm != TonemappingAlgorithm.none) { - algorithm = "clip"; + algorithmString = algorithm.ToString().ToLowerInvariant(); } tonemapArg = ":tonemapping=" + algorithm + ":peak_detect=0:color_primaries=bt709:color_trc=bt709:colorspace=bt709"; - if (string.Equals(range, "tv", StringComparison.OrdinalIgnoreCase) - || string.Equals(range, "pc", StringComparison.OrdinalIgnoreCase)) + if (range == TonemappingRange.tv || range == TonemappingRange.pc) { - tonemapArg += ":range=" + range; + tonemapArg += ":range=" + range.ToString().ToLowerInvariant(); } } @@ -3530,8 +3483,8 @@ namespace MediaBrowser.Controller.MediaEncoding tonemapArgs += $":param={options.TonemappingParam}"; } - if (string.Equals(options.TonemappingRange, "tv", StringComparison.OrdinalIgnoreCase) - || string.Equals(options.TonemappingRange, "pc", StringComparison.OrdinalIgnoreCase)) + var range = options.TonemappingRange; + if (range == TonemappingRange.tv || range == TonemappingRange.pc) { tonemapArgs += $":range={options.TonemappingRange}"; } @@ -3575,7 +3528,7 @@ namespace MediaBrowser.Controller.MediaEncoding EncodingOptions options, string vidEncoder) { - if (!string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) + if (options.HardwareAccelerationType != HardwareAccelerationType.nvenc) { return (null, null, null); } @@ -3777,7 +3730,7 @@ namespace MediaBrowser.Controller.MediaEncoding EncodingOptions options, string vidEncoder) { - if (!string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) + if (options.HardwareAccelerationType != HardwareAccelerationType.amf) { return (null, null, null); } @@ -3993,7 +3946,7 @@ namespace MediaBrowser.Controller.MediaEncoding EncodingOptions options, string vidEncoder) { - if (!string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + if (options.HardwareAccelerationType != HardwareAccelerationType.qsv) { return (null, null, null); } @@ -4543,7 +4496,7 @@ namespace MediaBrowser.Controller.MediaEncoding EncodingOptions options, string vidEncoder) { - if (!string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + if (options.HardwareAccelerationType != HardwareAccelerationType.vaapi) { return (null, null, null); } @@ -5247,7 +5200,7 @@ namespace MediaBrowser.Controller.MediaEncoding EncodingOptions options, string vidEncoder) { - if (!string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) + if (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox) { return (null, null, null); } @@ -5436,7 +5389,7 @@ namespace MediaBrowser.Controller.MediaEncoding EncodingOptions options, string vidEncoder) { - if (!string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase)) + if (options.HardwareAccelerationType != HardwareAccelerationType.rkmpp) { return (null, null, null); } @@ -5696,38 +5649,20 @@ namespace MediaBrowser.Controller.MediaEncoding List subFilters; List overlayFilters; - if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + (mainFilters, subFilters, overlayFilters) = options.HardwareAccelerationType switch { - (mainFilters, subFilters, overlayFilters) = GetVaapiVidFilterChain(state, options, outputVideoCodec); - } - else if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) - { - (mainFilters, subFilters, overlayFilters) = GetIntelVidFilterChain(state, options, outputVideoCodec); - } - else if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) - { - (mainFilters, subFilters, overlayFilters) = GetNvidiaVidFilterChain(state, options, outputVideoCodec); - } - else if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) - { - (mainFilters, subFilters, overlayFilters) = GetAmdVidFilterChain(state, options, outputVideoCodec); - } - else if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) - { - (mainFilters, subFilters, overlayFilters) = GetAppleVidFilterChain(state, options, outputVideoCodec); - } - else if (string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase)) - { - (mainFilters, subFilters, overlayFilters) = GetRkmppVidFilterChain(state, options, outputVideoCodec); - } - else - { - (mainFilters, subFilters, overlayFilters) = GetSwVidFilterChain(state, options, outputVideoCodec); - } + HardwareAccelerationType.vaapi => GetVaapiVidFilterChain(state, options, outputVideoCodec), + HardwareAccelerationType.amf => GetAmdVidFilterChain(state, options, outputVideoCodec), + HardwareAccelerationType.qsv => GetIntelVidFilterChain(state, options, outputVideoCodec), + HardwareAccelerationType.nvenc => GetNvidiaVidFilterChain(state, options, outputVideoCodec), + HardwareAccelerationType.videotoolbox => GetAppleVidFilterChain(state, options, outputVideoCodec), + HardwareAccelerationType.rkmpp => GetRkmppVidFilterChain(state, options, outputVideoCodec), + _ => GetSwVidFilterChain(state, options, outputVideoCodec), + }; - mainFilters?.RemoveAll(filter => string.IsNullOrEmpty(filter)); - subFilters?.RemoveAll(filter => string.IsNullOrEmpty(filter)); - overlayFilters?.RemoveAll(filter => string.IsNullOrEmpty(filter)); + mainFilters?.RemoveAll(string.IsNullOrEmpty); + subFilters?.RemoveAll(string.IsNullOrEmpty); + overlayFilters?.RemoveAll(string.IsNullOrEmpty); var framerate = GetFramerateParam(state); if (framerate.HasValue) @@ -5907,7 +5842,9 @@ namespace MediaBrowser.Controller.MediaEncoding return null; } - if (!string.IsNullOrEmpty(videoStream.Codec) && !string.IsNullOrEmpty(options.HardwareAccelerationType)) + var hardwareAccelerationType = options.HardwareAccelerationType; + + if (!string.IsNullOrEmpty(videoStream.Codec) && hardwareAccelerationType != HardwareAccelerationType.none) { var bitDepth = GetVideoColorBitDepth(state); @@ -5919,10 +5856,10 @@ namespace MediaBrowser.Controller.MediaEncoding || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))) { // RKMPP has H.264 Hi10P decoder - bool hasHardwareHi10P = string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase); + bool hasHardwareHi10P = hardwareAccelerationType == HardwareAccelerationType.rkmpp; // VideoToolbox on Apple Silicon has H.264 Hi10P mode enabled after macOS 14.6 - if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) + if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox) { var ver = Environment.OSVersion.Version; var arch = RuntimeInformation.OSArchitecture; @@ -5939,34 +5876,20 @@ namespace MediaBrowser.Controller.MediaEncoding } } - if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) - { - return GetQsvHwVidDecoder(state, options, videoStream, bitDepth); - } - - if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) + var decoder = hardwareAccelerationType switch { - return GetNvdecVidDecoder(state, options, videoStream, bitDepth); - } - - if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) - { - return GetAmfVidDecoder(state, options, videoStream, bitDepth); - } - - if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) - { - return GetVaapiVidDecoder(state, options, videoStream, bitDepth); - } - - if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) - { - return GetVideotoolboxVidDecoder(state, options, videoStream, bitDepth); - } + HardwareAccelerationType.vaapi => GetVaapiVidDecoder(state, options, videoStream, bitDepth), + HardwareAccelerationType.amf => GetAmfVidDecoder(state, options, videoStream, bitDepth), + HardwareAccelerationType.qsv => GetQsvHwVidDecoder(state, options, videoStream, bitDepth), + HardwareAccelerationType.nvenc => GetNvdecVidDecoder(state, options, videoStream, bitDepth), + HardwareAccelerationType.videotoolbox => GetVideotoolboxVidDecoder(state, options, videoStream, bitDepth), + HardwareAccelerationType.rkmpp => GetRkmppVidDecoder(state, options, videoStream, bitDepth), + _ => string.Empty + }; - if (string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(decoder)) { - return GetRkmppVidDecoder(state, options, videoStream, bitDepth); + return decoder; } } @@ -5981,7 +5904,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Avoid a second attempt if no hardware acceleration is being used - options.HardwareDecodingCodecs = Array.FindAll(options.HardwareDecodingCodecs, val => !string.Equals(val, whichCodec, StringComparison.OrdinalIgnoreCase)); + options.HardwareDecodingCodecs = options.HardwareDecodingCodecs.Where(c => !string.Equals(c, whichCodec, StringComparison.OrdinalIgnoreCase)).ToArray(); // leave blank so ffmpeg will decide return null; @@ -6062,6 +5985,7 @@ namespace MediaBrowser.Controller.MediaEncoding var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox"); var isRkmppSupported = isLinux && IsRkmppFullSupported(); var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCase); + var hardwareAccelerationType = options.HardwareAccelerationType; var ffmpegVersion = _mediaEncoder.EncoderVersion; @@ -6099,7 +6023,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Intel qsv/d3d11va/vaapi - if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + if (hardwareAccelerationType == HardwareAccelerationType.qsv) { if (options.PreferSystemNativeHwDecoder) { @@ -6125,7 +6049,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Nvidia cuda - if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) + if (hardwareAccelerationType == HardwareAccelerationType.nvenc) { if (isCudaSupported && isCodecAvailable) { @@ -6142,7 +6066,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Amd d3d11va - if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) + if (hardwareAccelerationType == HardwareAccelerationType.amf) { if (isD3d11Supported && isCodecAvailable) { @@ -6152,7 +6076,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Vaapi - if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) + if (hardwareAccelerationType == HardwareAccelerationType.vaapi && isVaapiSupported && isCodecAvailable) { @@ -6161,7 +6085,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Apple videotoolbox - if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase) + if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox && isVideotoolboxSupported && isCodecAvailable) { @@ -6169,7 +6093,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Rockchip rkmpp - if (string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase) + if (hardwareAccelerationType == HardwareAccelerationType.rkmpp && isRkmppSupported && isCodecAvailable) { @@ -6185,7 +6109,7 @@ namespace MediaBrowser.Controller.MediaEncoding var isLinux = OperatingSystem.IsLinux(); if ((!isWindows && !isLinux) - || !string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + || options.HardwareAccelerationType != HardwareAccelerationType.qsv) { return null; } @@ -6254,7 +6178,7 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetNvdecVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth) { if ((!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux()) - || !string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) + || options.HardwareAccelerationType != HardwareAccelerationType.nvenc) { return null; } @@ -6319,7 +6243,7 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetAmfVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth) { if (!OperatingSystem.IsWindows() - || !string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) + || options.HardwareAccelerationType != HardwareAccelerationType.amf) { return null; } @@ -6375,7 +6299,7 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetVaapiVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth) { if (!OperatingSystem.IsLinux() - || !string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + || options.HardwareAccelerationType != HardwareAccelerationType.vaapi) { return null; } @@ -6437,7 +6361,7 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetVideotoolboxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth) { if (!OperatingSystem.IsMacOS() - || !string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) + || options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox) { return null; } @@ -6485,7 +6409,7 @@ namespace MediaBrowser.Controller.MediaEncoding var isLinux = OperatingSystem.IsLinux(); if (!isLinux - || !string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase)) + || options.HardwareAccelerationType != HardwareAccelerationType.rkmpp) { return null; } @@ -6749,7 +6673,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.IsVideoRequest) { - if (!string.IsNullOrEmpty(state.InputContainer) && state.VideoType == VideoType.VideoFile && string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType)) + if (!string.IsNullOrEmpty(state.InputContainer) && state.VideoType == VideoType.VideoFile && encodingOptions.HardwareAccelerationType != HardwareAccelerationType.none) { var inputFormat = GetInputFormat(state.InputContainer); if (!string.IsNullOrEmpty(inputFormat)) @@ -6865,7 +6789,7 @@ namespace MediaBrowser.Controller.MediaEncoding state.SupportedAudioCodecs = supportedAudioCodecsList.ToArray(); - request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => _mediaEncoder.CanEncodeToAudioCodec(i)) + request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(_mediaEncoder.CanEncodeToAudioCodec) ?? state.SupportedAudioCodecs.FirstOrDefault(); } @@ -6991,7 +6915,7 @@ namespace MediaBrowser.Controller.MediaEncoding return " -codec:s:0 " + codec + " -disposition:s:0 default"; } - public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string defaultPreset) + public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, EncoderPreset defaultPreset) { // Get the output codec name var videoCodec = GetVideoEncoder(state, encodingOptions); @@ -7042,7 +6966,7 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Empty; } - public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoCodec, string defaultPreset) + public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoCodec, EncoderPreset defaultPreset) { var args = "-codec:v:0 " + videoCodec; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index a6a443f3d..caa9cb499 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -224,7 +224,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (OperatingSystem.IsLinux() && SupportsHwaccel("vaapi") && !string.IsNullOrEmpty(options.VaapiDevice) - && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + && options.HardwareAccelerationType == HardwareAccelerationType.vaapi) { _isVaapiDeviceAmd = validator.CheckVaapiDeviceByDriverName("Mesa Gallium driver", options.VaapiDevice); _isVaapiDeviceInteliHD = validator.CheckVaapiDeviceByDriverName("Intel iHD driver", options.VaapiDevice); @@ -799,11 +799,12 @@ namespace MediaBrowser.MediaEncoding.Encoder if (allowHwAccel && enableKeyFrameOnlyExtraction) { - var supportsKeyFrameOnly = (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && options.EnableEnhancedNvdecDecoder) - || (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && OperatingSystem.IsWindows()) - || (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && options.PreferSystemNativeHwDecoder) - || string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) - || string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase); + var hardwareAccelerationType = options.HardwareAccelerationType; + var supportsKeyFrameOnly = (hardwareAccelerationType == HardwareAccelerationType.nvenc && options.EnableEnhancedNvdecDecoder) + || (hardwareAccelerationType == HardwareAccelerationType.amf && OperatingSystem.IsWindows()) + || (hardwareAccelerationType == HardwareAccelerationType.qsv && options.PreferSystemNativeHwDecoder) + || hardwareAccelerationType == HardwareAccelerationType.vaapi + || hardwareAccelerationType == HardwareAccelerationType.videotoolbox; if (!supportsKeyFrameOnly) { // Disable hardware acceleration when the hardware decoder does not support keyframe only mode. @@ -817,7 +818,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (!allowHwAccel) { options.EnableHardwareEncoding = false; - options.HardwareAccelerationType = string.Empty; + options.HardwareAccelerationType = HardwareAccelerationType.none; options.EnableTonemapping = false; } @@ -861,7 +862,7 @@ namespace MediaBrowser.MediaEncoding.Encoder inputArg = "-threads " + threads + " " + inputArg; // HW accel args set a different input thread count, only set if disabled } - if (options.HardwareAccelerationType.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase) && _isLowPriorityHwDecodeSupported) + if (options.HardwareAccelerationType == HardwareAccelerationType.videotoolbox && _isLowPriorityHwDecodeSupported) { // VideoToolbox supports low priority decoding, which is useful for trickplay inputArg = "-hwaccel_flags +low_priority " + inputArg; diff --git a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs index 42f355b05..57557d55c 100644 --- a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs +++ b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs @@ -352,12 +352,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable { var audioCodec = state.ActualOutputAudioCodec; var videoCodec = state.ActualOutputVideoCodec; - var hardwareAccelerationTypeString = _serverConfigurationManager.GetEncodingOptions().HardwareAccelerationType; - HardwareEncodingType? hardwareAccelerationType = null; - if (Enum.TryParse(hardwareAccelerationTypeString, out var parsedHardwareAccelerationType)) - { - hardwareAccelerationType = parsedHardwareAccelerationType; - } + var hardwareAccelerationType = _serverConfigurationManager.GetEncodingOptions().HardwareAccelerationType; _sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo { diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 4c5213d4e..d67a2479f 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1819 // XML serialization handles collections improperly, so we need to use arrays + #nullable disable using MediaBrowser.Model.Entities; @@ -30,9 +32,9 @@ public class EncodingOptions EnableTonemapping = false; EnableVppTonemapping = false; EnableVideoToolboxTonemapping = false; - TonemappingAlgorithm = "bt2390"; - TonemappingMode = "auto"; - TonemappingRange = "auto"; + TonemappingAlgorithm = TonemappingAlgorithm.bt2390; + TonemappingMode = TonemappingMode.auto; + TonemappingRange = TonemappingRange.auto; TonemappingDesat = 0; TonemappingPeak = 100; TonemappingParam = 0; @@ -41,7 +43,7 @@ public class EncodingOptions H264Crf = 23; H265Crf = 28; DeinterlaceDoubleRate = false; - DeinterlaceMethod = "yadif"; + DeinterlaceMethod = DeinterlaceMethod.yadif; EnableDecodingColorDepth10Hevc = true; EnableDecodingColorDepth10Vp9 = true; // Enhanced Nvdec or system native decoder is required for DoVi to SDR tone-mapping. @@ -53,8 +55,8 @@ public class EncodingOptions AllowHevcEncoding = false; AllowAv1Encoding = false; EnableSubtitleExtraction = true; - AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = new[] { "mkv" }; - HardwareDecodingCodecs = new string[] { "h264", "vc1" }; + AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = ["mkv"]; + HardwareDecodingCodecs = ["h264", "vc1"]; } /// @@ -120,7 +122,7 @@ public class EncodingOptions /// /// Gets or sets the hardware acceleration type. /// - public string HardwareAccelerationType { get; set; } + public HardwareAccelerationType HardwareAccelerationType { get; set; } /// /// Gets or sets the FFmpeg path as set by the user via the UI. @@ -160,17 +162,17 @@ public class EncodingOptions /// /// Gets or sets the tone-mapping algorithm. /// - public string TonemappingAlgorithm { get; set; } + public TonemappingAlgorithm TonemappingAlgorithm { get; set; } /// /// Gets or sets the tone-mapping mode. /// - public string TonemappingMode { get; set; } + public TonemappingMode TonemappingMode { get; set; } /// /// Gets or sets the tone-mapping range. /// - public string TonemappingRange { get; set; } + public TonemappingRange TonemappingRange { get; set; } /// /// Gets or sets the tone-mapping desaturation. @@ -210,7 +212,7 @@ public class EncodingOptions /// /// Gets or sets the encoder preset. /// - public string EncoderPreset { get; set; } + public EncoderPreset? EncoderPreset { get; set; } /// /// Gets or sets a value indicating whether the framerate is doubled when deinterlacing. @@ -220,7 +222,7 @@ public class EncodingOptions /// /// Gets or sets the deinterlace method. /// - public string DeinterlaceMethod { get; set; } + public DeinterlaceMethod DeinterlaceMethod { get; set; } /// /// Gets or sets a value indicating whether 10bit HEVC decoding is enabled. diff --git a/MediaBrowser.Model/Entities/DeinterlaceMethod.cs b/MediaBrowser.Model/Entities/DeinterlaceMethod.cs new file mode 100644 index 000000000..d05aac433 --- /dev/null +++ b/MediaBrowser.Model/Entities/DeinterlaceMethod.cs @@ -0,0 +1,19 @@ +#pragma warning disable SA1300 // Lowercase required for backwards compat. + +namespace MediaBrowser.Model.Entities; + +/// +/// Enum containing deinterlace methods. +/// +public enum DeinterlaceMethod +{ + /// + /// YADIF. + /// + yadif = 0, + + /// + /// BWDIF. + /// + bwdif = 1 +} diff --git a/MediaBrowser.Model/Entities/EncoderPreset.cs b/MediaBrowser.Model/Entities/EncoderPreset.cs new file mode 100644 index 000000000..74c071433 --- /dev/null +++ b/MediaBrowser.Model/Entities/EncoderPreset.cs @@ -0,0 +1,64 @@ +#pragma warning disable SA1300 // Lowercase required for backwards compat. + +namespace MediaBrowser.Model.Entities; + +/// +/// Enum containing encoder presets. +/// +public enum EncoderPreset +{ + /// + /// Auto preset. + /// + auto = 0, + + /// + /// Placebo preset. + /// + placebo = 1, + + /// + /// Veryslow preset. + /// + veryslow = 2, + + /// + /// Slower preset. + /// + slower = 3, + + /// + /// Slow preset. + /// + slow = 4, + + /// + /// Medium preset. + /// + medium = 5, + + /// + /// Fast preset. + /// + fast = 6, + + /// + /// Faster preset. + /// + faster = 7, + + /// + /// Veryfast preset. + /// + veryfast = 8, + + /// + /// Superfast preset. + /// + superfast = 9, + + /// + /// Ultrafast preset. + /// + ultrafast = 10 +} diff --git a/MediaBrowser.Model/Entities/HardwareAccelerationType.cs b/MediaBrowser.Model/Entities/HardwareAccelerationType.cs new file mode 100644 index 000000000..198a2e00f --- /dev/null +++ b/MediaBrowser.Model/Entities/HardwareAccelerationType.cs @@ -0,0 +1,49 @@ +#pragma warning disable SA1300 // Lowercase required for backwards compat. + +namespace MediaBrowser.Model.Entities; + +/// +/// Enum containing hardware acceleration types. +/// +public enum HardwareAccelerationType +{ + /// + /// Software accelleration. + /// + none = 0, + + /// + /// AMD AMF. + /// + amf = 1, + + /// + /// Intel Quick Sync Video. + /// + qsv = 2, + + /// + /// NVIDIA NVENC. + /// + nvenc = 3, + + /// + /// Video4Linux2 V4L2M2M. + /// + v4l2m2m = 4, + + /// + /// Video Acceleration API (VAAPI). + /// + vaapi = 5, + + /// + /// Video ToolBox. + /// + videotoolbox = 6, + + /// + /// Rockchip Media Process Platform (RKMPP). + /// + rkmpp = 7 +} diff --git a/MediaBrowser.Model/Entities/TonemappingAlgorithm.cs b/MediaBrowser.Model/Entities/TonemappingAlgorithm.cs new file mode 100644 index 000000000..488006e0b --- /dev/null +++ b/MediaBrowser.Model/Entities/TonemappingAlgorithm.cs @@ -0,0 +1,49 @@ +#pragma warning disable SA1300 // Lowercase required for backwards compat. + +namespace MediaBrowser.Model.Entities; + +/// +/// Enum containing tonemapping algorithms. +/// +public enum TonemappingAlgorithm +{ + /// + /// None. + /// + none = 0, + + /// + /// Clip. + /// + clip = 1, + + /// + /// Linear. + /// + linear = 2, + + /// + /// Gamma. + /// + gamma = 3, + + /// + /// Reinhard. + /// + reinhard = 4, + + /// + /// Hable. + /// + hable = 5, + + /// + /// Mobius. + /// + mobius = 6, + + /// + /// BT2390. + /// + bt2390 = 7 +} diff --git a/MediaBrowser.Model/Entities/TonemappingMode.cs b/MediaBrowser.Model/Entities/TonemappingMode.cs new file mode 100644 index 000000000..e10a0b4ad --- /dev/null +++ b/MediaBrowser.Model/Entities/TonemappingMode.cs @@ -0,0 +1,34 @@ +#pragma warning disable SA1300 // Lowercase required for backwards compat. + +namespace MediaBrowser.Model.Entities; + +/// +/// Enum containing tonemapping modes. +/// +public enum TonemappingMode +{ + /// + /// Auto. + /// + auto = 0, + + /// + /// Max. + /// + max = 1, + + /// + /// RGB. + /// + rgb = 2, + + /// + /// Lum. + /// + lum = 3, + + /// + /// ITP. + /// + itp = 4 +} diff --git a/MediaBrowser.Model/Entities/TonemappingRange.cs b/MediaBrowser.Model/Entities/TonemappingRange.cs new file mode 100644 index 000000000..b1446b81c --- /dev/null +++ b/MediaBrowser.Model/Entities/TonemappingRange.cs @@ -0,0 +1,24 @@ +#pragma warning disable SA1300 // Lowercase required for backwards compat. + +namespace MediaBrowser.Model.Entities; + +/// +/// Enum containing tonemapping ranges. +/// +public enum TonemappingRange +{ + /// + /// Auto. + /// + auto = 0, + + /// + /// TV. + /// + tv = 1, + + /// + /// PC. + /// + pc = 2 +} diff --git a/MediaBrowser.Model/Session/HardwareEncodingType.cs b/MediaBrowser.Model/Session/HardwareEncodingType.cs deleted file mode 100644 index cf424fef5..000000000 --- a/MediaBrowser.Model/Session/HardwareEncodingType.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace MediaBrowser.Model.Session -{ - /// - /// Enum HardwareEncodingType. - /// - public enum HardwareEncodingType - { - /// - /// AMD AMF. - /// - AMF = 0, - - /// - /// Intel Quick Sync Video. - /// - QSV = 1, - - /// - /// NVIDIA NVENC. - /// - NVENC = 2, - - /// - /// Video4Linux2 V4L2. - /// - V4L2M2M = 3, - - /// - /// Video Acceleration API (VAAPI). - /// - VAAPI = 4, - - /// - /// Video ToolBox. - /// - VideoToolBox = 5, - - /// - /// Rockchip Media Process Platform (RKMPP). - /// - RKMPP = 6 - } -} diff --git a/MediaBrowser.Model/Session/TranscodingInfo.cs b/MediaBrowser.Model/Session/TranscodingInfo.cs index 000cbd4c5..ae25267ac 100644 --- a/MediaBrowser.Model/Session/TranscodingInfo.cs +++ b/MediaBrowser.Model/Session/TranscodingInfo.cs @@ -1,34 +1,76 @@ #nullable disable -#pragma warning disable CS1591 -namespace MediaBrowser.Model.Session +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Model.Session; + +/// +/// Class holding information on a runnning transcode. +/// +public class TranscodingInfo { - public class TranscodingInfo - { - public string AudioCodec { get; set; } + /// + /// Gets or sets the thread count used for encoding. + /// + public string AudioCodec { get; set; } - public string VideoCodec { get; set; } + /// + /// Gets or sets the thread count used for encoding. + /// + public string VideoCodec { get; set; } - public string Container { get; set; } + /// + /// Gets or sets the thread count used for encoding. + /// + public string Container { get; set; } - public bool IsVideoDirect { get; set; } + /// + /// Gets or sets a value indicating whether the video is passed through. + /// + public bool IsVideoDirect { get; set; } - public bool IsAudioDirect { get; set; } + /// + /// Gets or sets a value indicating whether the audio is passed through. + /// + public bool IsAudioDirect { get; set; } - public int? Bitrate { get; set; } + /// + /// Gets or sets the bitrate. + /// + public int? Bitrate { get; set; } - public float? Framerate { get; set; } + /// + /// Gets or sets the framerate. + /// + public float? Framerate { get; set; } - public double? CompletionPercentage { get; set; } + /// + /// Gets or sets the completion percentage. + /// + public double? CompletionPercentage { get; set; } - public int? Width { get; set; } + /// + /// Gets or sets the video width. + /// + public int? Width { get; set; } - public int? Height { get; set; } + /// + /// Gets or sets the video height. + /// + public int? Height { get; set; } - public int? AudioChannels { get; set; } + /// + /// Gets or sets the audio channels. + /// + public int? AudioChannels { get; set; } - public HardwareEncodingType? HardwareAccelerationType { get; set; } + /// + /// Gets or sets the hardware acceleration type. + /// + public HardwareAccelerationType? HardwareAccelerationType { get; set; } - public TranscodeReason TranscodeReasons { get; set; } - } + /// + /// Gets or sets the transcode reasons. + /// + public TranscodeReason TranscodeReasons { get; set; } } diff --git a/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs b/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs index 9a023d7ed..1846ba26b 100644 --- a/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs +++ b/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs @@ -128,7 +128,7 @@ public class DynamicHlsPlaylistGenerator : IDynamicHlsPlaylistGenerator return false; } - internal static bool IsExtractionAllowedForFile(ReadOnlySpan filePath, string[] allowedExtensions) + internal static bool IsExtractionAllowedForFile(ReadOnlySpan filePath, IReadOnlyList allowedExtensions) { var extension = Path.GetExtension(filePath); if (extension.IsEmpty) @@ -138,7 +138,7 @@ public class DynamicHlsPlaylistGenerator : IDynamicHlsPlaylistGenerator // Remove the leading dot var extensionWithoutDot = extension[1..]; - for (var i = 0; i < allowedExtensions.Length; i++) + for (var i = 0; i < allowedExtensions.Count; i++) { var allowedExtension = allowedExtensions[i].AsSpan().TrimStart('.'); if (extensionWithoutDot.Equals(allowedExtension, StringComparison.OrdinalIgnoreCase)) -- cgit v1.2.3 From 987dbe98c8ab55c5c8eb563820e54453c835cdde Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 10 Sep 2024 03:17:10 +0800 Subject: cli: add option to disable network change detection (#11253) --- Emby.Server.Implementations/ConfigurationOptions.cs | 3 ++- Jellyfin.Server/StartupOptions.cs | 11 +++++++++++ MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs | 5 +++++ src/Jellyfin.Networking/Manager/NetworkManager.cs | 9 +++++++-- 4 files changed, 25 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index e86010513..702707297 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -20,7 +20,8 @@ namespace Emby.Server.Implementations { PlaylistsAllowDuplicatesKey, bool.FalseString }, { BindToUnixSocketKey, bool.FalseString }, { SqliteCacheSizeKey, "20000" }, - { FfmpegSkipValidationKey, bool.FalseString } + { FfmpegSkipValidationKey, bool.FalseString }, + { DetectNetworkChangeKey, bool.TrueString } }; } } diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index c3989751c..91ac827ca 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -67,6 +67,12 @@ namespace Jellyfin.Server [Option("published-server-url", Required = false, HelpText = "Jellyfin Server URL to publish via auto discover process")] public string? PublishedServerUrl { get; set; } + /// + /// Gets or sets a value indicating whether the server should not detect network status change. + /// + [Option("nonetchange", Required = false, HelpText = "Indicates that the server should not detect network status change.")] + public bool NoDetectNetworkChange { get; set; } + /// /// Gets the command line options as a dictionary that can be used in the .NET configuration system. /// @@ -90,6 +96,11 @@ namespace Jellyfin.Server config.Add(FfmpegPathKey, FFmpegPath); } + if (NoDetectNetworkChange) + { + config.Add(DetectNetworkChangeKey, bool.FalseString); + } + return config; } } diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index 0aaf4fcd9..7ca508426 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -69,6 +69,11 @@ namespace MediaBrowser.Controller.Extensions /// public const string SqliteCacheSizeKey = "sqlite:cacheSize"; + /// + /// The key for a setting that indicates whether the application should detect network status change. + /// + public const string DetectNetworkChangeKey = "DetectNetworkChange"; + /// /// Gets a value indicating whether the application should host static web content from the . /// diff --git a/src/Jellyfin.Networking/Manager/NetworkManager.cs b/src/Jellyfin.Networking/Manager/NetworkManager.cs index cf6a2cc55..b285b836b 100644 --- a/src/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/src/Jellyfin.Networking/Manager/NetworkManager.cs @@ -97,10 +97,15 @@ public class NetworkManager : INetworkManager, IDisposable _networkEventLock = new object(); _remoteAddressFilter = new List(); + _ = bool.TryParse(startupConfig[DetectNetworkChangeKey], out var detectNetworkChange); + UpdateSettings(_configurationManager.GetNetworkConfiguration()); - NetworkChange.NetworkAddressChanged += OnNetworkAddressChanged; - NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged; + if (detectNetworkChange) + { + NetworkChange.NetworkAddressChanged += OnNetworkAddressChanged; + NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged; + } _configurationManager.NamedConfigurationUpdated += ConfigurationUpdated; } -- cgit v1.2.3 From 81aca67745d541e276cfa05efe57e52e9f5a5d0e Mon Sep 17 00:00:00 2001 From: Gary Wang Date: Wed, 11 Sep 2024 23:36:56 +0800 Subject: feat(i18n): able to finetune transliterator (#12378) --- src/Jellyfin.Extensions/StringExtensions.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/Jellyfin.Extensions/StringExtensions.cs b/src/Jellyfin.Extensions/StringExtensions.cs index 8cfebd594..4b9677d9f 100644 --- a/src/Jellyfin.Extensions/StringExtensions.cs +++ b/src/Jellyfin.Extensions/StringExtensions.cs @@ -9,8 +9,21 @@ namespace Jellyfin.Extensions /// public static partial class StringExtensions { - private static readonly Lazy _transliterator = new(() => Transliterator.GetInstance( - "Any-Latin; Latin-Ascii; Lower; NFD; [:Nonspacing Mark:] Remove; [:Punctuation:] Remove;")); + private static readonly Lazy _transliteratorId = new(() => + Environment.GetEnvironmentVariable("JELLYFIN_TRANSLITERATOR_ID") + ?? "Any-Latin; Latin-Ascii; Lower; NFD; [:Nonspacing Mark:] Remove; [:Punctuation:] Remove;"); + + private static readonly Lazy _transliterator = new(() => + { + try + { + return Transliterator.GetInstance(_transliteratorId.Value); + } + catch (ArgumentException) + { + return null; + } + }); // Matches non-conforming unicode chars // https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/ @@ -108,7 +121,7 @@ namespace Jellyfin.Extensions /// The transliterated string. public static string Transliterated(this string text) { - return _transliterator.Value.Transliterate(text); + return (_transliterator.Value is null) ? text : _transliterator.Value.Transliterate(text); } } } -- cgit v1.2.3 From 7a2427bf07f9036d62c88a75855cd6dc7e8e3064 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Thu, 5 Sep 2024 12:55:15 +0200 Subject: Add SessionInfoDto, DeviceInfoDto and implement JsonDelimitedArrayConverter.Write --- .../Session/SessionManager.cs | 137 ++++++++++++++- Jellyfin.Api/Controllers/DevicesController.cs | 10 +- Jellyfin.Api/Controllers/SessionController.cs | 85 ++-------- .../Models/SessionDtos/ClientCapabilitiesDto.cs | 83 --------- Jellyfin.Data/Dtos/DeviceOptionsDto.cs | 33 ++-- .../Devices/DeviceManager.cs | 85 ++++++++-- .../Authentication/AuthenticationResult.cs | 33 ++-- MediaBrowser.Controller/Devices/IDeviceManager.cs | 150 ++++++++++------- .../AuthenticationResultEventArgs.cs | 3 +- .../WebSocketMessages/Outbound/SessionsMessage.cs | 5 +- MediaBrowser.Controller/Session/ISessionManager.cs | 11 ++ MediaBrowser.Controller/Session/SessionInfo.cs | 120 +++++++++++-- MediaBrowser.Model/Devices/DeviceInfo.cs | 119 +++++++------ MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs | 69 ++++++++ MediaBrowser.Model/Dto/DeviceInfoDto.cs | 83 +++++++++ MediaBrowser.Model/Dto/SessionInfoDto.cs | 186 +++++++++++++++++++++ .../Json/Converters/JsonDelimitedArrayConverter.cs | 65 ++++--- .../Converters/JsonCommaDelimitedArrayTests.cs | 16 +- 18 files changed, 919 insertions(+), 374 deletions(-) delete mode 100644 Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs create mode 100644 MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs create mode 100644 MediaBrowser.Model/Dto/DeviceInfoDto.cs create mode 100644 MediaBrowser.Model/Dto/SessionInfoDto.cs (limited to 'src') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 72e164b52..6bcbe3ceb 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -68,13 +66,29 @@ namespace Emby.Server.Implementations.Session private Timer _inactiveTimer; private DtoOptions _itemInfoDtoOptions; - private bool _disposed = false; + private bool _disposed; + /// + /// Initializes a new instance of the class. + /// + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. public SessionManager( ILogger logger, IEventManager eventManager, IUserDataManager userDataManager, - IServerConfigurationManager config, + IServerConfigurationManager serverConfigurationManager, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, @@ -88,7 +102,7 @@ namespace Emby.Server.Implementations.Session _logger = logger; _eventManager = eventManager; _userDataManager = userDataManager; - _config = config; + _config = serverConfigurationManager; _libraryManager = libraryManager; _userManager = userManager; _musicManager = musicManager; @@ -508,7 +522,10 @@ namespace Emby.Server.Implementations.Session deviceName = "Network Device"; } - var deviceOptions = _deviceManager.GetDeviceOptions(deviceId); + var deviceOptions = _deviceManager.GetDeviceOptions(deviceId) ?? new() + { + DeviceId = deviceId + }; if (string.IsNullOrEmpty(deviceOptions.CustomName)) { sessionInfo.DeviceName = deviceName; @@ -1076,6 +1093,42 @@ namespace Emby.Server.Implementations.Session return session; } + private SessionInfoDto ToSessionInfoDto(SessionInfo sessionInfo) + { + return new SessionInfoDto + { + PlayState = sessionInfo.PlayState, + AdditionalUsers = sessionInfo.AdditionalUsers, + Capabilities = _deviceManager.ToClientCapabilitiesDto(sessionInfo.Capabilities), + RemoteEndPoint = sessionInfo.RemoteEndPoint, + PlayableMediaTypes = sessionInfo.PlayableMediaTypes, + Id = sessionInfo.Id, + UserId = sessionInfo.UserId, + UserName = sessionInfo.UserName, + Client = sessionInfo.Client, + LastActivityDate = sessionInfo.LastActivityDate, + LastPlaybackCheckIn = sessionInfo.LastPlaybackCheckIn, + LastPausedDate = sessionInfo.LastPausedDate, + DeviceName = sessionInfo.DeviceName, + DeviceType = sessionInfo.DeviceType, + NowPlayingItem = sessionInfo.NowPlayingItem, + NowViewingItem = sessionInfo.NowViewingItem, + DeviceId = sessionInfo.DeviceId, + ApplicationVersion = sessionInfo.ApplicationVersion, + TranscodingInfo = sessionInfo.TranscodingInfo, + IsActive = sessionInfo.IsActive, + SupportsMediaControl = sessionInfo.SupportsMediaControl, + SupportsRemoteControl = sessionInfo.SupportsRemoteControl, + NowPlayingQueue = sessionInfo.NowPlayingQueue, + NowPlayingQueueFullItems = sessionInfo.NowPlayingQueueFullItems, + HasCustomDeviceName = sessionInfo.HasCustomDeviceName, + PlaylistItemId = sessionInfo.PlaylistItemId, + ServerId = sessionInfo.ServerId, + UserPrimaryImageTag = sessionInfo.UserPrimaryImageTag, + SupportedCommands = sessionInfo.SupportedCommands + }; + } + /// public Task SendMessageCommand(string controllingSessionId, string sessionId, MessageCommand command, CancellationToken cancellationToken) { @@ -1393,7 +1446,7 @@ namespace Emby.Server.Implementations.Session UserName = user.Username }; - session.AdditionalUsers = [..session.AdditionalUsers, newUser]; + session.AdditionalUsers = [.. session.AdditionalUsers, newUser]; } } @@ -1505,7 +1558,7 @@ namespace Emby.Server.Implementations.Session var returnResult = new AuthenticationResult { User = _userManager.GetUserDto(user, request.RemoteEndPoint), - SessionInfo = session, + SessionInfo = ToSessionInfoDto(session), AccessToken = token, ServerId = _appHost.SystemId }; @@ -1800,6 +1853,74 @@ namespace Emby.Server.Implementations.Session return await GetSessionByAuthenticationToken(items[0], deviceId, remoteEndpoint, null).ConfigureAwait(false); } + /// + public IReadOnlyList GetSessions( + Guid userId, + string deviceId, + int? activeWithinSeconds, + Guid? controllableUserToCheck) + { + var result = Sessions; + var user = _userManager.GetUserById(userId); + if (!string.IsNullOrEmpty(deviceId)) + { + result = result.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); + } + + if (!controllableUserToCheck.IsNullOrEmpty()) + { + result = result.Where(i => i.SupportsRemoteControl); + + var controlledUser = _userManager.GetUserById(controllableUserToCheck.Value); + if (controlledUser is null) + { + return []; + } + + if (!controlledUser.HasPermission(PermissionKind.EnableSharedDeviceControl)) + { + // Controlled user has device sharing disabled + result = result.Where(i => !i.UserId.IsEmpty()); + } + + if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers)) + { + // User cannot control other user's sessions, validate user id. + result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(controllableUserToCheck.Value)); + } + + result = result.Where(i => + { + if (!string.IsNullOrWhiteSpace(i.DeviceId) && !_deviceManager.CanAccessDevice(user, i.DeviceId)) + { + return false; + } + + return true; + }); + } + else if (!user.HasPermission(PermissionKind.IsAdministrator)) + { + // Request isn't from administrator, limit to "own" sessions. + result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(userId)); + + // Don't report acceleration type for non-admin users. + result = result.Select(r => + { + r.TranscodingInfo.HardwareAccelerationType = HardwareAccelerationType.none; + return r; + }); + } + + if (activeWithinSeconds.HasValue && activeWithinSeconds.Value > 0) + { + var minActiveDate = DateTime.UtcNow.AddSeconds(0 - activeWithinSeconds.Value); + result = result.Where(i => i.LastActivityDate >= minActiveDate); + } + + return result.Select(ToSessionInfoDto).ToList(); + } + /// public Task SendMessageToAdminSessions(SessionMessageType name, T data, CancellationToken cancellationToken) { diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index 2a2ab4ad1..50050262f 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -1,15 +1,13 @@ using System; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; -using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using Jellyfin.Data.Dtos; -using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Queries; using MediaBrowser.Common.Api; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Devices; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -47,7 +45,7 @@ public class DevicesController : BaseJellyfinApiController /// An containing the list of devices. [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetDevices([FromQuery] Guid? userId) + public ActionResult> GetDevices([FromQuery] Guid? userId) { userId = RequestHelpers.GetUserId(User, userId); return _deviceManager.GetDevicesForUser(userId); @@ -63,7 +61,7 @@ public class DevicesController : BaseJellyfinApiController [HttpGet("Info")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult GetDeviceInfo([FromQuery, Required] string id) + public ActionResult GetDeviceInfo([FromQuery, Required] string id) { var deviceInfo = _deviceManager.GetDevice(id); if (deviceInfo is null) @@ -84,7 +82,7 @@ public class DevicesController : BaseJellyfinApiController [HttpGet("Options")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult GetDeviceOptions([FromQuery, Required] string id) + public ActionResult GetDeviceOptions([FromQuery, Required] string id) { var deviceInfo = _deviceManager.GetDeviceOptions(id); if (deviceInfo is null) diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 942bdeb9e..91a879b8e 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -1,18 +1,13 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; -using Jellyfin.Api.Models.SessionDtos; using Jellyfin.Data.Enums; -using Jellyfin.Extensions; using MediaBrowser.Common.Api; -using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; @@ -32,22 +27,18 @@ public class SessionController : BaseJellyfinApiController { private readonly ISessionManager _sessionManager; private readonly IUserManager _userManager; - private readonly IDeviceManager _deviceManager; /// /// Initializes a new instance of the class. /// /// Instance of interface. /// Instance of interface. - /// Instance of interface. public SessionController( ISessionManager sessionManager, - IUserManager userManager, - IDeviceManager deviceManager) + IUserManager userManager) { _sessionManager = sessionManager; _userManager = userManager; - _deviceManager = deviceManager; } /// @@ -57,77 +48,25 @@ public class SessionController : BaseJellyfinApiController /// Filter by device Id. /// Optional. Filter by sessions that were active in the last n seconds. /// List of sessions returned. - /// An with the available sessions. + /// An with the available sessions. [HttpGet("Sessions")] [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetSessions( + public ActionResult> GetSessions( [FromQuery] Guid? controllableByUserId, [FromQuery] string? deviceId, [FromQuery] int? activeWithinSeconds) { - var result = _sessionManager.Sessions; - var isRequestingFromAdmin = User.IsInRole(UserRoles.Administrator); - - if (!string.IsNullOrEmpty(deviceId)) - { - result = result.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); - } - - if (!controllableByUserId.IsNullOrEmpty()) + Guid? controllableUserToCheck = controllableByUserId is null ? null : RequestHelpers.GetUserId(User, controllableByUserId); + var result = _sessionManager.GetSessions( + User.GetUserId(), + deviceId, + activeWithinSeconds, + controllableUserToCheck); + + if (result.Count == 0) { - result = result.Where(i => i.SupportsRemoteControl); - - var user = _userManager.GetUserById(controllableByUserId.Value); - if (user is null) - { - return NotFound(); - } - - if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers)) - { - // User cannot control other user's sessions, validate user id. - result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(RequestHelpers.GetUserId(User, controllableByUserId))); - } - - if (!user.HasPermission(PermissionKind.EnableSharedDeviceControl)) - { - result = result.Where(i => !i.UserId.IsEmpty()); - } - - result = result.Where(i => - { - if (!string.IsNullOrWhiteSpace(i.DeviceId)) - { - if (!_deviceManager.CanAccessDevice(user, i.DeviceId)) - { - return false; - } - } - - return true; - }); - } - else if (!isRequestingFromAdmin) - { - // Request isn't from administrator, limit to "own" sessions. - result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(User.GetUserId())); - } - - if (activeWithinSeconds.HasValue && activeWithinSeconds.Value > 0) - { - var minActiveDate = DateTime.UtcNow.AddSeconds(0 - activeWithinSeconds.Value); - result = result.Where(i => i.LastActivityDate >= minActiveDate); - } - - // Request isn't from administrator, don't report acceleration type. - if (!isRequestingFromAdmin) - { - result = result.Select(r => - { - r.TranscodingInfo.HardwareAccelerationType = HardwareAccelerationType.none; - return r; - }); + return NotFound(); } return Ok(result); diff --git a/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs b/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs deleted file mode 100644 index c699c469d..000000000 --- a/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Text.Json.Serialization; -using Jellyfin.Data.Enums; -using Jellyfin.Extensions.Json.Converters; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Session; - -namespace Jellyfin.Api.Models.SessionDtos; - -/// -/// Client capabilities dto. -/// -public class ClientCapabilitiesDto -{ - /// - /// Gets or sets the list of playable media types. - /// - [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] - public IReadOnlyList PlayableMediaTypes { get; set; } = Array.Empty(); - - /// - /// Gets or sets the list of supported commands. - /// - [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] - public IReadOnlyList SupportedCommands { get; set; } = Array.Empty(); - - /// - /// Gets or sets a value indicating whether session supports media control. - /// - public bool SupportsMediaControl { get; set; } - - /// - /// Gets or sets a value indicating whether session supports a persistent identifier. - /// - public bool SupportsPersistentIdentifier { get; set; } - - /// - /// Gets or sets the device profile. - /// - public DeviceProfile? DeviceProfile { get; set; } - - /// - /// Gets or sets the app store url. - /// - public string? AppStoreUrl { get; set; } - - /// - /// Gets or sets the icon url. - /// - public string? IconUrl { get; set; } - -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member - // TODO: Remove after 10.9 - [Obsolete("Unused")] - [DefaultValue(false)] - public bool? SupportsContentUploading { get; set; } = false; - - // TODO: Remove after 10.9 - [Obsolete("Unused")] - [DefaultValue(false)] - public bool? SupportsSync { get; set; } = false; -#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member - - /// - /// Convert the dto to the full model. - /// - /// The converted model. - public ClientCapabilities ToClientCapabilities() - { - return new ClientCapabilities - { - PlayableMediaTypes = PlayableMediaTypes, - SupportedCommands = SupportedCommands, - SupportsMediaControl = SupportsMediaControl, - SupportsPersistentIdentifier = SupportsPersistentIdentifier, - DeviceProfile = DeviceProfile, - AppStoreUrl = AppStoreUrl, - IconUrl = IconUrl - }; - } -} diff --git a/Jellyfin.Data/Dtos/DeviceOptionsDto.cs b/Jellyfin.Data/Dtos/DeviceOptionsDto.cs index 392ef5ff4..aad578709 100644 --- a/Jellyfin.Data/Dtos/DeviceOptionsDto.cs +++ b/Jellyfin.Data/Dtos/DeviceOptionsDto.cs @@ -1,23 +1,22 @@ -namespace Jellyfin.Data.Dtos +namespace Jellyfin.Data.Dtos; + +/// +/// A dto representing custom options for a device. +/// +public class DeviceOptionsDto { /// - /// A dto representing custom options for a device. + /// Gets or sets the id. /// - public class DeviceOptionsDto - { - /// - /// Gets or sets the id. - /// - public int Id { get; set; } + public int Id { get; set; } - /// - /// Gets or sets the device id. - /// - public string? DeviceId { get; set; } + /// + /// Gets or sets the device id. + /// + public string? DeviceId { get; set; } - /// - /// Gets or sets the custom name. - /// - public string? CustomName { get; set; } - } + /// + /// Gets or sets the custom name. + /// + public string? CustomName { get; set; } } diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs index 415c04bbf..d3bff2936 100644 --- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs +++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Jellyfin.Data.Dtos; using Jellyfin.Data.Entities; using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Enums; @@ -13,6 +14,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Devices; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; using Microsoft.EntityFrameworkCore; @@ -68,7 +70,7 @@ namespace Jellyfin.Server.Implementations.Devices } /// - public async Task UpdateDeviceOptions(string deviceId, string deviceName) + public async Task UpdateDeviceOptions(string deviceId, string? deviceName) { DeviceOptions? deviceOptions; var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); @@ -105,29 +107,37 @@ namespace Jellyfin.Server.Implementations.Devices } /// - public DeviceOptions GetDeviceOptions(string deviceId) + public DeviceOptionsDto? GetDeviceOptions(string deviceId) { - _deviceOptions.TryGetValue(deviceId, out var deviceOptions); + if (_deviceOptions.TryGetValue(deviceId, out var deviceOptions)) + { + return ToDeviceOptionsDto(deviceOptions); + } - return deviceOptions ?? new DeviceOptions(deviceId); + return null; } /// - public ClientCapabilities GetCapabilities(string deviceId) + public ClientCapabilities GetCapabilities(string? deviceId) { + if (deviceId is null) + { + return new(); + } + return _capabilitiesMap.TryGetValue(deviceId, out ClientCapabilities? result) ? result - : new ClientCapabilities(); + : new(); } /// - public DeviceInfo? GetDevice(string id) + public DeviceInfoDto? GetDevice(string id) { var device = _devices.Values.Where(d => d.DeviceId == id).OrderByDescending(d => d.DateLastActivity).FirstOrDefault(); _deviceOptions.TryGetValue(id, out var deviceOption); var deviceInfo = device is null ? null : ToDeviceInfo(device, deviceOption); - return deviceInfo; + return deviceInfo is null ? null : ToDeviceInfoDto(deviceInfo); } /// @@ -166,7 +176,7 @@ namespace Jellyfin.Server.Implementations.Devices } /// - public QueryResult GetDevicesForUser(Guid? userId) + public QueryResult GetDevicesForUser(Guid? userId) { IEnumerable devices = _devices.Values .OrderByDescending(d => d.DateLastActivity) @@ -187,9 +197,11 @@ namespace Jellyfin.Server.Implementations.Devices { _deviceOptions.TryGetValue(device.DeviceId, out var option); return ToDeviceInfo(device, option); - }).ToArray(); + }) + .Select(ToDeviceInfoDto) + .ToArray(); - return new QueryResult(array); + return new QueryResult(array); } /// @@ -235,13 +247,9 @@ namespace Jellyfin.Server.Implementations.Devices private DeviceInfo ToDeviceInfo(Device authInfo, DeviceOptions? options = null) { var caps = GetCapabilities(authInfo.DeviceId); - var user = _userManager.GetUserById(authInfo.UserId); - if (user is null) - { - throw new ResourceNotFoundException("User with UserId " + authInfo.UserId + " not found"); - } + var user = _userManager.GetUserById(authInfo.UserId) ?? throw new ResourceNotFoundException("User with UserId " + authInfo.UserId + " not found"); - return new DeviceInfo + return new() { AppName = authInfo.AppName, AppVersion = authInfo.AppVersion, @@ -254,5 +262,48 @@ namespace Jellyfin.Server.Implementations.Devices CustomName = options?.CustomName, }; } + + private DeviceOptionsDto ToDeviceOptionsDto(DeviceOptions options) + { + return new() + { + Id = options.Id, + DeviceId = options.DeviceId, + CustomName = options.CustomName, + }; + } + + private DeviceInfoDto ToDeviceInfoDto(DeviceInfo info) + { + return new() + { + Name = info.Name, + CustomName = info.CustomName, + AccessToken = info.AccessToken, + Id = info.Id, + LastUserName = info.LastUserName, + AppName = info.AppName, + AppVersion = info.AppVersion, + LastUserId = info.LastUserId, + DateLastActivity = info.DateLastActivity, + Capabilities = ToClientCapabilitiesDto(info.Capabilities), + IconUrl = info.IconUrl + }; + } + + /// + public ClientCapabilitiesDto ToClientCapabilitiesDto(ClientCapabilities capabilities) + { + return new() + { + PlayableMediaTypes = capabilities.PlayableMediaTypes, + SupportedCommands = capabilities.SupportedCommands, + SupportsMediaControl = capabilities.SupportsMediaControl, + SupportsPersistentIdentifier = capabilities.SupportsPersistentIdentifier, + DeviceProfile = capabilities.DeviceProfile, + AppStoreUrl = capabilities.AppStoreUrl, + IconUrl = capabilities.IconUrl + }; + } } } diff --git a/MediaBrowser.Controller/Authentication/AuthenticationResult.cs b/MediaBrowser.Controller/Authentication/AuthenticationResult.cs index 635e4eb3d..daf4d9631 100644 --- a/MediaBrowser.Controller/Authentication/AuthenticationResult.cs +++ b/MediaBrowser.Controller/Authentication/AuthenticationResult.cs @@ -1,20 +1,31 @@ #nullable disable -#pragma warning disable CS1591 - -using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; -namespace MediaBrowser.Controller.Authentication +namespace MediaBrowser.Controller.Authentication; + +/// +/// A class representing an authentication result. +/// +public class AuthenticationResult { - public class AuthenticationResult - { - public UserDto User { get; set; } + /// + /// Gets or sets the user. + /// + public UserDto User { get; set; } - public SessionInfo SessionInfo { get; set; } + /// + /// Gets or sets the session info. + /// + public SessionInfoDto SessionInfo { get; set; } - public string AccessToken { get; set; } + /// + /// Gets or sets the access token. + /// + public string AccessToken { get; set; } - public string ServerId { get; set; } - } + /// + /// Gets or sets the server id. + /// + public string ServerId { get; set; } } diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index 5566421cb..cade53d99 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -1,81 +1,117 @@ -#nullable disable - -#pragma warning disable CS1591 - using System; using System.Threading.Tasks; +using Jellyfin.Data.Dtos; using Jellyfin.Data.Entities; using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Events; using Jellyfin.Data.Queries; using MediaBrowser.Model.Devices; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; -namespace MediaBrowser.Controller.Devices +namespace MediaBrowser.Controller.Devices; + +/// +/// Device manager interface. +/// +public interface IDeviceManager { - public interface IDeviceManager - { - event EventHandler>> DeviceOptionsUpdated; + /// + /// Event handler for updated device options. + /// + event EventHandler>> DeviceOptionsUpdated; + + /// + /// Creates a new device. + /// + /// The device to create. + /// A representing the creation of the device. + Task CreateDevice(Device device); - /// - /// Creates a new device. - /// - /// The device to create. - /// A representing the creation of the device. - Task CreateDevice(Device device); + /// + /// Saves the capabilities. + /// + /// The device id. + /// The capabilities. + void SaveCapabilities(string deviceId, ClientCapabilities capabilities); - /// - /// Saves the capabilities. - /// - /// The device id. - /// The capabilities. - void SaveCapabilities(string deviceId, ClientCapabilities capabilities); + /// + /// Gets the capabilities. + /// + /// The device id. + /// ClientCapabilities. + ClientCapabilities GetCapabilities(string? deviceId); - /// - /// Gets the capabilities. - /// - /// The device id. - /// ClientCapabilities. - ClientCapabilities GetCapabilities(string deviceId); + /// + /// Gets the device information. + /// + /// The identifier. + /// DeviceInfoDto. + DeviceInfoDto? GetDevice(string id); - /// - /// Gets the device information. - /// - /// The identifier. - /// DeviceInfo. - DeviceInfo GetDevice(string id); + /// + /// Gets devices based on the provided query. + /// + /// The device query. + /// A representing the retrieval of the devices. + QueryResult GetDevices(DeviceQuery query); - /// - /// Gets devices based on the provided query. - /// - /// The device query. - /// A representing the retrieval of the devices. - QueryResult GetDevices(DeviceQuery query); + /// + /// Gets device infromation based on the provided query. + /// + /// The device query. + /// A representing the retrieval of the device information. + QueryResult GetDeviceInfos(DeviceQuery query); - QueryResult GetDeviceInfos(DeviceQuery query); + /// + /// Gets the device information. + /// + /// The user's id, or null. + /// IEnumerable<DeviceInfoDto>. + QueryResult GetDevicesForUser(Guid? userId); - /// - /// Gets the devices. - /// - /// The user's id, or null. - /// IEnumerable<DeviceInfo>. - QueryResult GetDevicesForUser(Guid? userId); + /// + /// Deletes a device. + /// + /// The device. + /// A representing the deletion of the device. + Task DeleteDevice(Device device); - Task DeleteDevice(Device device); + /// + /// Updates a device. + /// + /// The device. + /// A representing the update of the device. + Task UpdateDevice(Device device); - Task UpdateDevice(Device device); + /// + /// Determines whether this instance [can access device] the specified user identifier. + /// + /// The user to test. + /// The device id to test. + /// Whether the user can access the device. + bool CanAccessDevice(User user, string deviceId); - /// - /// Determines whether this instance [can access device] the specified user identifier. - /// - /// The user to test. - /// The device id to test. - /// Whether the user can access the device. - bool CanAccessDevice(User user, string deviceId); + /// + /// Updates the options of a device. + /// + /// The device id. + /// The device name. + /// A representing the update of the device options. + Task UpdateDeviceOptions(string deviceId, string? deviceName); - Task UpdateDeviceOptions(string deviceId, string deviceName); + /// + /// Gets the options of a device. + /// + /// The device id. + /// of the device. + DeviceOptionsDto? GetDeviceOptions(string deviceId); - DeviceOptions GetDeviceOptions(string deviceId); - } + /// + /// Gets the dto for client capabilites. + /// + /// The client capabilities. + /// of the device. + ClientCapabilitiesDto ToClientCapabilitiesDto(ClientCapabilities capabilities); } diff --git a/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs b/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs index 357ef9406..1542c58b3 100644 --- a/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs +++ b/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs @@ -1,6 +1,5 @@ using System; using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; namespace MediaBrowser.Controller.Events.Authentication; @@ -29,7 +28,7 @@ public class AuthenticationResultEventArgs : EventArgs /// /// Gets or sets the session information. /// - public SessionInfo? SessionInfo { get; set; } + public SessionInfoDto? SessionInfo { get; set; } /// /// Gets or sets the server id. diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs index 3504831b8..833074541 100644 --- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs +++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.ComponentModel; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Session; namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound; @@ -8,13 +9,13 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound; /// /// Sessions message. /// -public class SessionsMessage : OutboundWebSocketMessage> +public class SessionsMessage : OutboundWebSocketMessage> { /// /// Initializes a new instance of the class. /// /// Session info. - public SessionsMessage(IReadOnlyList data) + public SessionsMessage(IReadOnlyList data) : base(data) { } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 5a47236f9..f2e98dd78 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Jellyfin.Data.Entities.Security; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Session; using MediaBrowser.Model.SyncPlay; @@ -292,6 +293,16 @@ namespace MediaBrowser.Controller.Session /// SessionInfo. SessionInfo GetSession(string deviceId, string client, string version); + /// + /// Gets all sessions available to a user. + /// + /// The session identifier. + /// The device id. + /// Active within session limit. + /// Filter for sessions remote controllable for this user. + /// IReadOnlyList{SessionInfoDto}. + IReadOnlyList GetSessions(Guid userId, string deviceId, int? activeWithinSeconds, Guid? controllableUserToCheck); + /// /// Gets the session by authentication token. /// diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 9e3358818..3ba1bfce4 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Linq; @@ -27,28 +25,45 @@ namespace MediaBrowser.Controller.Session private readonly ISessionManager _sessionManager; private readonly ILogger _logger; - private readonly object _progressLock = new object(); + private readonly object _progressLock = new(); private Timer _progressTimer; private PlaybackProgressInfo _lastProgressInfo; - private bool _disposed = false; + private bool _disposed; + /// + /// Initializes a new instance of the class. + /// + /// Instance of interface. + /// Instance of interface. public SessionInfo(ISessionManager sessionManager, ILogger logger) { _sessionManager = sessionManager; _logger = logger; - AdditionalUsers = Array.Empty(); + AdditionalUsers = []; PlayState = new PlayerStateInfo(); - SessionControllers = Array.Empty(); - NowPlayingQueue = Array.Empty(); - NowPlayingQueueFullItems = Array.Empty(); + SessionControllers = []; + NowPlayingQueue = []; + NowPlayingQueueFullItems = []; } + /// + /// Gets or sets the play state. + /// + /// The play state. public PlayerStateInfo PlayState { get; set; } - public SessionUserInfo[] AdditionalUsers { get; set; } + /// + /// Gets or sets the additional users. + /// + /// The additional users. + public IReadOnlyList AdditionalUsers { get; set; } + /// + /// Gets or sets the client capabilities. + /// + /// The client capabilities. public ClientCapabilities Capabilities { get; set; } /// @@ -67,7 +82,7 @@ namespace MediaBrowser.Controller.Session { if (Capabilities is null) { - return Array.Empty(); + return []; } return Capabilities.PlayableMediaTypes; @@ -134,9 +149,17 @@ namespace MediaBrowser.Controller.Session /// The now playing item. public BaseItemDto NowPlayingItem { get; set; } + /// + /// Gets or sets the now playing queue full items. + /// + /// The now playing queue full items. [JsonIgnore] public BaseItem FullNowPlayingItem { get; set; } + /// + /// Gets or sets the now viewing item. + /// + /// The now viewing item. public BaseItemDto NowViewingItem { get; set; } /// @@ -156,8 +179,12 @@ namespace MediaBrowser.Controller.Session /// /// The session controller. [JsonIgnore] - public ISessionController[] SessionControllers { get; set; } + public IReadOnlyList SessionControllers { get; set; } + /// + /// Gets or sets the transcoding info. + /// + /// The transcoding info. public TranscodingInfo TranscodingInfo { get; set; } /// @@ -177,7 +204,7 @@ namespace MediaBrowser.Controller.Session } } - if (controllers.Length > 0) + if (controllers.Count > 0) { return false; } @@ -186,6 +213,10 @@ namespace MediaBrowser.Controller.Session } } + /// + /// Gets a value indicating whether the session supports media control. + /// + /// true if this session supports media control; otherwise, false. public bool SupportsMediaControl { get @@ -208,6 +239,10 @@ namespace MediaBrowser.Controller.Session } } + /// + /// Gets a value indicating whether the session supports remote control. + /// + /// true if this session supports remote control; otherwise, false. public bool SupportsRemoteControl { get @@ -230,16 +265,40 @@ namespace MediaBrowser.Controller.Session } } + /// + /// Gets or sets the now playing queue. + /// + /// The now playing queue. public IReadOnlyList NowPlayingQueue { get; set; } + /// + /// Gets or sets the now playing queue full items. + /// + /// The now playing queue full items. public IReadOnlyList NowPlayingQueueFullItems { get; set; } + /// + /// Gets or sets a value indicating whether the session has a custom device name. + /// + /// true if this session has a custom device name; otherwise, false. public bool HasCustomDeviceName { get; set; } + /// + /// Gets or sets the playlist item id. + /// + /// The splaylist item id. public string PlaylistItemId { get; set; } + /// + /// Gets or sets the server id. + /// + /// The server id. public string ServerId { get; set; } + /// + /// Gets or sets the user primary image tag. + /// + /// The user primary image tag. public string UserPrimaryImageTag { get; set; } /// @@ -247,8 +306,14 @@ namespace MediaBrowser.Controller.Session /// /// The supported commands. public IReadOnlyList SupportedCommands - => Capabilities is null ? Array.Empty() : Capabilities.SupportedCommands; + => Capabilities is null ? [] : Capabilities.SupportedCommands; + /// + /// Ensures a controller of type exists. + /// + /// Class to register. + /// The factory. + /// Tuple{ISessionController, bool}. public Tuple EnsureController(Func factory) { var controllers = SessionControllers.ToList(); @@ -261,18 +326,27 @@ namespace MediaBrowser.Controller.Session } var newController = factory(this); - _logger.LogDebug("Creating new {0}", newController.GetType().Name); + _logger.LogDebug("Creating new {Factory}", newController.GetType().Name); controllers.Add(newController); - SessionControllers = controllers.ToArray(); + SessionControllers = [.. controllers]; return new Tuple(newController, true); } + /// + /// Adds a controller to the session. + /// + /// The controller. public void AddController(ISessionController controller) { - SessionControllers = [..SessionControllers, controller]; + SessionControllers = [.. SessionControllers, controller]; } + /// + /// Gets a value indicating whether the session contains a user. + /// + /// The user id to check. + /// true if this session contains the user; otherwise, false. public bool ContainsUser(Guid userId) { if (UserId.Equals(userId)) @@ -291,6 +365,11 @@ namespace MediaBrowser.Controller.Session return false; } + /// + /// Starts automatic progressing. + /// + /// The playback progress info. + /// The supported commands. public void StartAutomaticProgress(PlaybackProgressInfo progressInfo) { if (_disposed) @@ -359,6 +438,9 @@ namespace MediaBrowser.Controller.Session } } + /// + /// Stops automatic progressing. + /// public void StopAutomaticProgress() { lock (_progressLock) @@ -373,6 +455,10 @@ namespace MediaBrowser.Controller.Session } } + /// + /// Disposes the instance async. + /// + /// ValueTask. public async ValueTask DisposeAsync() { _disposed = true; @@ -380,7 +466,7 @@ namespace MediaBrowser.Controller.Session StopAutomaticProgress(); var controllers = SessionControllers.ToList(); - SessionControllers = Array.Empty(); + SessionControllers = []; foreach (var controller in controllers) { diff --git a/MediaBrowser.Model/Devices/DeviceInfo.cs b/MediaBrowser.Model/Devices/DeviceInfo.cs index 4962992a0..115598613 100644 --- a/MediaBrowser.Model/Devices/DeviceInfo.cs +++ b/MediaBrowser.Model/Devices/DeviceInfo.cs @@ -1,69 +1,84 @@ -#nullable disable -#pragma warning disable CS1591 - using System; using MediaBrowser.Model.Session; -namespace MediaBrowser.Model.Devices +namespace MediaBrowser.Model.Devices; + +/// +/// A class for device Information. +/// +public class DeviceInfo { - public class DeviceInfo + /// + /// Initializes a new instance of the class. + /// + public DeviceInfo() { - public DeviceInfo() - { - Capabilities = new ClientCapabilities(); - } + Capabilities = new ClientCapabilities(); + } - public string Name { get; set; } + /// + /// Gets or sets the name. + /// + /// The name. + public string? Name { get; set; } - public string CustomName { get; set; } + /// + /// Gets or sets the custom name. + /// + /// The custom name. + public string? CustomName { get; set; } - /// - /// Gets or sets the access token. - /// - public string AccessToken { get; set; } + /// + /// Gets or sets the access token. + /// + /// The access token. + public string? AccessToken { get; set; } - /// - /// Gets or sets the identifier. - /// - /// The identifier. - public string Id { get; set; } + /// + /// Gets or sets the identifier. + /// + /// The identifier. + public string? Id { get; set; } - /// - /// Gets or sets the last name of the user. - /// - /// The last name of the user. - public string LastUserName { get; set; } + /// + /// Gets or sets the last name of the user. + /// + /// The last name of the user. + public string? LastUserName { get; set; } - /// - /// Gets or sets the name of the application. - /// - /// The name of the application. - public string AppName { get; set; } + /// + /// Gets or sets the name of the application. + /// + /// The name of the application. + public string? AppName { get; set; } - /// - /// Gets or sets the application version. - /// - /// The application version. - public string AppVersion { get; set; } + /// + /// Gets or sets the application version. + /// + /// The application version. + public string? AppVersion { get; set; } - /// - /// Gets or sets the last user identifier. - /// - /// The last user identifier. - public Guid LastUserId { get; set; } + /// + /// Gets or sets the last user identifier. + /// + /// The last user identifier. + public Guid? LastUserId { get; set; } - /// - /// Gets or sets the date last modified. - /// - /// The date last modified. - public DateTime DateLastActivity { get; set; } + /// + /// Gets or sets the date last modified. + /// + /// The date last modified. + public DateTime? DateLastActivity { get; set; } - /// - /// Gets or sets the capabilities. - /// - /// The capabilities. - public ClientCapabilities Capabilities { get; set; } + /// + /// Gets or sets the capabilities. + /// + /// The capabilities. + public ClientCapabilities Capabilities { get; set; } - public string IconUrl { get; set; } - } + /// + /// Gets or sets the icon URL. + /// + /// The icon URL. + public string? IconUrl { get; set; } } diff --git a/MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs b/MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs new file mode 100644 index 000000000..5963ed270 --- /dev/null +++ b/MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; +using Jellyfin.Extensions.Json.Converters; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Session; + +namespace MediaBrowser.Model.Dto; + +/// +/// Client capabilities dto. +/// +public class ClientCapabilitiesDto +{ + /// + /// Gets or sets the list of playable media types. + /// + [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + public IReadOnlyList PlayableMediaTypes { get; set; } = []; + + /// + /// Gets or sets the list of supported commands. + /// + [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + public IReadOnlyList SupportedCommands { get; set; } = []; + + /// + /// Gets or sets a value indicating whether session supports media control. + /// + public bool SupportsMediaControl { get; set; } + + /// + /// Gets or sets a value indicating whether session supports a persistent identifier. + /// + public bool SupportsPersistentIdentifier { get; set; } + + /// + /// Gets or sets the device profile. + /// + public DeviceProfile? DeviceProfile { get; set; } + + /// + /// Gets or sets the app store url. + /// + public string? AppStoreUrl { get; set; } + + /// + /// Gets or sets the icon url. + /// + public string? IconUrl { get; set; } + + /// + /// Convert the dto to the full model. + /// + /// The converted model. + public ClientCapabilities ToClientCapabilities() + { + return new ClientCapabilities + { + PlayableMediaTypes = PlayableMediaTypes, + SupportedCommands = SupportedCommands, + SupportsMediaControl = SupportsMediaControl, + SupportsPersistentIdentifier = SupportsPersistentIdentifier, + DeviceProfile = DeviceProfile, + AppStoreUrl = AppStoreUrl, + IconUrl = IconUrl + }; + } +} diff --git a/MediaBrowser.Model/Dto/DeviceInfoDto.cs b/MediaBrowser.Model/Dto/DeviceInfoDto.cs new file mode 100644 index 000000000..ac7a731a9 --- /dev/null +++ b/MediaBrowser.Model/Dto/DeviceInfoDto.cs @@ -0,0 +1,83 @@ +using System; + +namespace MediaBrowser.Model.Dto; + +/// +/// A DTO representing device information. +/// +public class DeviceInfoDto +{ + /// + /// Initializes a new instance of the class. + /// + public DeviceInfoDto() + { + Capabilities = new ClientCapabilitiesDto(); + } + + /// + /// Gets or sets the name. + /// + /// The name. + public string? Name { get; set; } + + /// + /// Gets or sets the custom name. + /// + /// The custom name. + public string? CustomName { get; set; } + + /// + /// Gets or sets the access token. + /// + /// The access token. + public string? AccessToken { get; set; } + + /// + /// Gets or sets the identifier. + /// + /// The identifier. + public string? Id { get; set; } + + /// + /// Gets or sets the last name of the user. + /// + /// The last name of the user. + public string? LastUserName { get; set; } + + /// + /// Gets or sets the name of the application. + /// + /// The name of the application. + public string? AppName { get; set; } + + /// + /// Gets or sets the application version. + /// + /// The application version. + public string? AppVersion { get; set; } + + /// + /// Gets or sets the last user identifier. + /// + /// The last user identifier. + public Guid? LastUserId { get; set; } + + /// + /// Gets or sets the date last modified. + /// + /// The date last modified. + public DateTime? DateLastActivity { get; set; } + + /// + /// Gets or sets the capabilities. + /// + /// The capabilities. + public ClientCapabilitiesDto Capabilities { get; set; } + + /// + /// Gets or sets the icon URL. + /// + /// The icon URL. + public string? IconUrl { get; set; } +} diff --git a/MediaBrowser.Model/Dto/SessionInfoDto.cs b/MediaBrowser.Model/Dto/SessionInfoDto.cs new file mode 100644 index 000000000..2496c933a --- /dev/null +++ b/MediaBrowser.Model/Dto/SessionInfoDto.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using Jellyfin.Data.Enums; +using MediaBrowser.Model.Session; + +namespace MediaBrowser.Model.Dto; + +/// +/// Session info DTO. +/// +public class SessionInfoDto +{ + /// + /// Gets or sets the play state. + /// + /// The play state. + public PlayerStateInfo? PlayState { get; set; } + + /// + /// Gets or sets the additional users. + /// + /// The additional users. + public IReadOnlyList? AdditionalUsers { get; set; } + + /// + /// Gets or sets the client capabilities. + /// + /// The client capabilities. + public ClientCapabilitiesDto? Capabilities { get; set; } + + /// + /// Gets or sets the remote end point. + /// + /// The remote end point. + public string? RemoteEndPoint { get; set; } + + /// + /// Gets or sets the playable media types. + /// + /// The playable media types. + public IReadOnlyList PlayableMediaTypes { get; set; } = []; + + /// + /// Gets or sets the id. + /// + /// The id. + public string? Id { get; set; } + + /// + /// Gets or sets the user id. + /// + /// The user id. + public Guid UserId { get; set; } + + /// + /// Gets or sets the username. + /// + /// The username. + public string? UserName { get; set; } + + /// + /// Gets or sets the type of the client. + /// + /// The type of the client. + public string? Client { get; set; } + + /// + /// Gets or sets the last activity date. + /// + /// The last activity date. + public DateTime LastActivityDate { get; set; } + + /// + /// Gets or sets the last playback check in. + /// + /// The last playback check in. + public DateTime LastPlaybackCheckIn { get; set; } + + /// + /// Gets or sets the last paused date. + /// + /// The last paused date. + public DateTime? LastPausedDate { get; set; } + + /// + /// Gets or sets the name of the device. + /// + /// The name of the device. + public string? DeviceName { get; set; } + + /// + /// Gets or sets the type of the device. + /// + /// The type of the device. + public string? DeviceType { get; set; } + + /// + /// Gets or sets the now playing item. + /// + /// The now playing item. + public BaseItemDto? NowPlayingItem { get; set; } + + /// + /// Gets or sets the now viewing item. + /// + /// The now viewing item. + public BaseItemDto? NowViewingItem { get; set; } + + /// + /// Gets or sets the device id. + /// + /// The device id. + public string? DeviceId { get; set; } + + /// + /// Gets or sets the application version. + /// + /// The application version. + public string? ApplicationVersion { get; set; } + + /// + /// Gets or sets the transcoding info. + /// + /// The transcoding info. + public TranscodingInfo? TranscodingInfo { get; set; } + + /// + /// Gets or sets a value indicating whether this session is active. + /// + /// true if this session is active; otherwise, false. + public bool IsActive { get; set; } + + /// + /// Gets or sets a value indicating whether the session supports media control. + /// + /// true if this session supports media control; otherwise, false. + public bool SupportsMediaControl { get; set; } + + /// + /// Gets or sets a value indicating whether the session supports remote control. + /// + /// true if this session supports remote control; otherwise, false. + public bool SupportsRemoteControl { get; set; } + + /// + /// Gets or sets the now playing queue. + /// + /// The now playing queue. + public IReadOnlyList? NowPlayingQueue { get; set; } + + /// + /// Gets or sets the now playing queue full items. + /// + /// The now playing queue full items. + public IReadOnlyList? NowPlayingQueueFullItems { get; set; } + + /// + /// Gets or sets a value indicating whether the session has a custom device name. + /// + /// true if this session has a custom device name; otherwise, false. + public bool HasCustomDeviceName { get; set; } + + /// + /// Gets or sets the playlist item id. + /// + /// The splaylist item id. + public string? PlaylistItemId { get; set; } + + /// + /// Gets or sets the server id. + /// + /// The server id. + public string? ServerId { get; set; } + + /// + /// Gets or sets the user primary image tag. + /// + /// The user primary image tag. + public string? UserPrimaryImageTag { get; set; } + + /// + /// Gets or sets the supported commands. + /// + /// The supported commands. + public IReadOnlyList SupportedCommands { get; set; } = []; +} diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs index 1466d3a71..b9477ce6b 100644 --- a/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs +++ b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; @@ -35,38 +37,27 @@ namespace Jellyfin.Extensions.Json.Converters var stringEntries = reader.GetString()!.Split(Delimiter, StringSplitOptions.RemoveEmptyEntries); if (stringEntries.Length == 0) { - return Array.Empty(); + return []; } - var parsedValues = new object[stringEntries.Length]; - var convertedCount = 0; + var typedValues = new List(); for (var i = 0; i < stringEntries.Length; i++) { try { - parsedValues[i] = _typeConverter.ConvertFromInvariantString(stringEntries[i].Trim()) ?? throw new FormatException(); - convertedCount++; + var parsedValue = _typeConverter.ConvertFromInvariantString(stringEntries[i].Trim()); + if (parsedValue is not null) + { + typedValues.Add((T)parsedValue); + } } catch (FormatException) { - // TODO log when upgraded to .Net6 - // https://github.com/dotnet/runtime/issues/42975 - // _logger.LogDebug(e, "Error converting value."); + // Ignore unconvertable inputs } } - var typedValues = new T[convertedCount]; - var typedValueIndex = 0; - for (var i = 0; i < stringEntries.Length; i++) - { - if (parsedValues[i] is not null) - { - typedValues.SetValue(parsedValues[i], typedValueIndex); - typedValueIndex++; - } - } - - return typedValues; + return [.. typedValues]; } return JsonSerializer.Deserialize(ref reader, options); @@ -75,7 +66,39 @@ namespace Jellyfin.Extensions.Json.Converters /// public override void Write(Utf8JsonWriter writer, T[]? value, JsonSerializerOptions options) { - throw new NotImplementedException(); + if (value is not null) + { + writer.WriteStartArray(); + if (value.Length > 0) + { + var toWrite = value.Length - 1; + foreach (var it in value) + { + var wrote = false; + if (it is not null) + { + writer.WriteStringValue(it.ToString()); + wrote = true; + } + + if (toWrite > 0) + { + if (wrote) + { + writer.WriteStringValue(Delimiter.ToString()); + } + + toWrite--; + } + } + } + + writer.WriteEndArray(); + } + else + { + writer.WriteNullValue(); + } } } } diff --git a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs index 61105b42b..9fc015823 100644 --- a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs +++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs @@ -41,7 +41,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel { - Value = new[] { "a", "b", "c" } + Value = ["a", "b", "c"] }; var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""a,b,c"" }", _jsonOptions); @@ -53,7 +53,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel { - Value = new[] { "a", "b", "c" } + Value = ["a", "b", "c"] }; var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""a, b, c"" }", _jsonOptions); @@ -65,7 +65,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] }; var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp,MoveDown"" }", _jsonOptions); @@ -77,7 +77,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] }; var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp,,MoveDown"" }", _jsonOptions); @@ -89,7 +89,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] }; var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp,TotallyNotAVallidCommand,MoveDown"" }", _jsonOptions); @@ -101,7 +101,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] }; var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp, MoveDown"" }", _jsonOptions); @@ -113,7 +113,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel { - Value = new[] { "a", "b", "c" } + Value = ["a", "b", "c"] }; var value = JsonSerializer.Deserialize>(@"{ ""Value"": [""a"",""b"",""c""] }", _jsonOptions); @@ -125,7 +125,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] }; var value = JsonSerializer.Deserialize>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", _jsonOptions); -- cgit v1.2.3 From d4bde14a0143ae60db5187084dbb8cbf93e77cfd Mon Sep 17 00:00:00 2001 From: Tim Eisele Date: Thu, 19 Sep 2024 16:46:59 +0200 Subject: Update src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs Co-authored-by: Bond-009 --- src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs index b9477ce6b..936a5a97c 100644 --- a/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs +++ b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs @@ -57,7 +57,7 @@ namespace Jellyfin.Extensions.Json.Converters } } - return [.. typedValues]; + return typedValues.ToArray(); } return JsonSerializer.Deserialize(ref reader, options); -- cgit v1.2.3 From b496f979f02c6b65a2a6f1887558d8cbd9d8d7b6 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Thu, 3 Oct 2024 14:18:40 +0000 Subject: Clean up deprecated -vsync option (#12765) --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 2 +- .../MediaEncoding/EncodingHelper.cs | 32 ++++++++++++++++++++-- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 2 +- src/Jellyfin.LiveTv/IO/EncodedRecorder.cs | 4 +-- 4 files changed, 34 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 48fda471a..54e0527c9 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1908,7 +1908,7 @@ public class DynamicHlsController : BaseJellyfinApiController if (!string.IsNullOrEmpty(state.OutputVideoSync)) { - args += " -vsync " + state.OutputVideoSync; + args += EncodingHelper.GetVideoSyncOption(state.OutputVideoSync, _mediaEncoder.EncoderVersion); } args += _encodingHelper.GetOutputFFlags(state); diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index ae33da1fb..c120e08fa 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -6857,7 +6857,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(state.InputVideoSync)) { - inputModifier += " -vsync " + state.InputVideoSync; + inputModifier += GetVideoSyncOption(state.InputVideoSync, _mediaEncoder.EncoderVersion); } if (state.ReadInputAtNativeFramerate && state.InputProtocol != MediaProtocol.Rtsp) @@ -7280,7 +7280,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(state.OutputVideoSync)) { - args += " -vsync " + state.OutputVideoSync; + args += GetVideoSyncOption(state.OutputVideoSync, _mediaEncoder.EncoderVersion); } args += GetOutputFFlags(state); @@ -7453,5 +7453,33 @@ namespace MediaBrowser.Controller.MediaEncoding return state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode || (state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding && !IsCopyCodec(state.OutputVideoCodec)); } + + public static string GetVideoSyncOption(string videoSync, Version encoderVersion) + { + if (string.IsNullOrEmpty(videoSync)) + { + return string.Empty; + } + + if (encoderVersion >= new Version(5, 1)) + { + if (int.TryParse(videoSync, CultureInfo.InvariantCulture, out var vsync)) + { + return vsync switch + { + -1 => " -fps_mode auto", + 0 => " -fps_mode passthrough", + 1 => " -fps_mode cfr", + 2 => " -fps_mode vfr", + _ => string.Empty + }; + } + + return string.Empty; + } + + // -vsync is deprecated in FFmpeg 5.1 and will be removed in the future. + return $" -vsync {videoSync}"; + } } } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index ace8bda19..ec0401576 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -945,7 +945,7 @@ namespace MediaBrowser.MediaEncoding.Encoder vidEncoder, encoderQualityOption + encoderQuality + " ", vidEncoder.Contains("videotoolbox", StringComparison.InvariantCultureIgnoreCase) ? "-allow_sw 1 " : string.Empty, // allow_sw fallback for some intel macs - EncoderVersion >= new Version(5, 1) ? "-fps_mode passthrough " : "-vsync passthrough ", // passthrough timestamp + EncodingHelper.GetVideoSyncOption("0", EncoderVersion).Trim() + " ", // passthrough timestamp "image2", outputPath); diff --git a/src/Jellyfin.LiveTv/IO/EncodedRecorder.cs b/src/Jellyfin.LiveTv/IO/EncodedRecorder.cs index ff00c8999..0c660637f 100644 --- a/src/Jellyfin.LiveTv/IO/EncodedRecorder.cs +++ b/src/Jellyfin.LiveTv/IO/EncodedRecorder.cs @@ -130,7 +130,7 @@ namespace Jellyfin.LiveTv.IO const int MaxBitrate = 25000000; videoArgs = string.Format( CultureInfo.InvariantCulture, - "-codec:v:0 libx264 -force_key_frames \"expr:gte(t,n_forced*5)\" {0} -pix_fmt yuv420p -preset superfast -crf 23 -b:v {1} -maxrate {1} -bufsize ({1}*2) -vsync -1 -profile:v high -level 41", + "-codec:v:0 libx264 -force_key_frames \"expr:gte(t,n_forced*5)\" {0} -pix_fmt yuv420p -preset superfast -crf 23 -b:v {1} -maxrate {1} -bufsize ({1}*2) -profile:v high -level 41", GetOutputSizeParam(), MaxBitrate); } @@ -157,7 +157,7 @@ namespace Jellyfin.LiveTv.IO flags.Add("+genpts"); } - var inputModifier = "-async 1 -vsync -1"; + var inputModifier = "-async 1"; if (flags.Count > 0) { -- cgit v1.2.3