From aa99aaebc4e37ca1e16c11f72dd4a57038200179 Mon Sep 17 00:00:00 2001 From: knackebrot Date: Mon, 7 Nov 2022 00:15:04 +0100 Subject: Add audio vbr calculation --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 29 ++++++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) (limited to 'Jellyfin.Api/Controllers/DynamicHlsController.cs') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 4d8b4de24..7b1830761 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1685,14 +1685,25 @@ public class DynamicHlsController : BaseJellyfinApiController audioTranscodeParams += "-acodec " + audioCodec; - if (state.OutputAudioBitrate.HasValue) + var audioBitrate = state.OutputAudioBitrate; + var audioChannels = state.OutputAudioChannels; + + if (audioBitrate.HasValue) { - audioTranscodeParams += " -ab " + state.OutputAudioBitrate.Value.ToString(CultureInfo.InvariantCulture); + string vbrParam; + if (_encodingOptions.EnableAudioVbr && (vbrParam = _encodingHelper.GetAudioVbrModeParam(state.OutputAudioCodec, audioBitrate.Value / audioChannels ?? 2)) != null) + { + audioTranscodeParams += vbrParam; + } + else + { + audioTranscodeParams += " -ab " + audioBitrate.Value.ToString(CultureInfo.InvariantCulture); + } } - if (state.OutputAudioChannels.HasValue) + if (audioChannels.HasValue) { - audioTranscodeParams += " -ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture); + audioTranscodeParams += " -ac " + audioChannels.Value.ToString(CultureInfo.InvariantCulture); } if (state.OutputAudioSampleRate.HasValue) @@ -1747,7 +1758,15 @@ public class DynamicHlsController : BaseJellyfinApiController if (bitrate.HasValue) { - args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture); + string vbrParam; + if (_encodingOptions.EnableAudioVbr && (vbrParam = _encodingHelper.GetAudioVbrModeParam(state.OutputAudioCodec, bitrate.Value / channels ?? 2)) != null) + { + args += vbrParam; + } + else + { + args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture); + } } if (state.OutputAudioSampleRate.HasValue) -- cgit v1.2.3 From 16f2cca882de67622aa80a1964a788a2977253ff Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Tue, 28 Feb 2023 15:12:43 +0100 Subject: Apply review suggestions --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'Jellyfin.Api/Controllers/DynamicHlsController.cs') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 7b1830761..40ca2fcf7 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1690,8 +1690,8 @@ public class DynamicHlsController : BaseJellyfinApiController if (audioBitrate.HasValue) { - string vbrParam; - if (_encodingOptions.EnableAudioVbr && (vbrParam = _encodingHelper.GetAudioVbrModeParam(state.OutputAudioCodec, audioBitrate.Value / audioChannels ?? 2)) != null) + var vbrParam = _encodingHelper.GetAudioVbrModeParam(state.OutputAudioCodec, audioBitrate.Value / (audioChannels ?? 2)); + if (_encodingOptions.EnableAudioVbr && vbrParam is not null) { audioTranscodeParams += vbrParam; } @@ -1758,8 +1758,8 @@ public class DynamicHlsController : BaseJellyfinApiController if (bitrate.HasValue) { - string vbrParam; - if (_encodingOptions.EnableAudioVbr && (vbrParam = _encodingHelper.GetAudioVbrModeParam(state.OutputAudioCodec, bitrate.Value / channels ?? 2)) != null) + var vbrParam = _encodingHelper.GetAudioVbrModeParam(state.OutputAudioCodec, bitrate.Value / (channels ?? 2)); + if (_encodingOptions.EnableAudioVbr && vbrParam is not null) { args += vbrParam; } -- cgit v1.2.3 From 1f15724398c1e05a3c5b72b7d7bd062413529890 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Thu, 2 Mar 2023 20:57:59 +0100 Subject: Use source audio bitrate if requested codec is lossless --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 18 ++++----- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 13 ++++++- Jellyfin.Api/Helpers/StreamingHelpers.cs | 16 +++++--- .../MediaEncoding/EncodingHelper.cs | 45 ++++++++-------------- 4 files changed, 48 insertions(+), 44 deletions(-) (limited to 'Jellyfin.Api/Controllers/DynamicHlsController.cs') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 40ca2fcf7..4232d4c8a 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -12,6 +12,7 @@ using Jellyfin.Api.Attributes; using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.PlaybackDtos; using Jellyfin.Api.Models.StreamingDtos; +using Jellyfin.Extensions; using Jellyfin.MediaEncoding.Hls.Playlist; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; @@ -1688,7 +1689,7 @@ public class DynamicHlsController : BaseJellyfinApiController var audioBitrate = state.OutputAudioBitrate; var audioChannels = state.OutputAudioChannels; - if (audioBitrate.HasValue) + if (audioBitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(state.ActualOutputAudioCodec, StringComparison.OrdinalIgnoreCase)) { var vbrParam = _encodingHelper.GetAudioVbrModeParam(state.OutputAudioCodec, audioBitrate.Value / (audioChannels ?? 2)); if (_encodingOptions.EnableAudioVbr && vbrParam is not null) @@ -1717,11 +1718,11 @@ public class DynamicHlsController : BaseJellyfinApiController // dts, flac, opus and truehd are experimental in mp4 muxer var strictArgs = string.Empty; - - if (string.Equals(state.ActualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase) - || string.Equals(state.ActualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase) - || string.Equals(state.ActualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase) - || string.Equals(state.ActualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase)) + var actualOutputAudioCodec = state.ActualOutputAudioCodec; + if (string.Equals(actualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase) + || string.Equals(actualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase) + || string.Equals(actualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase) + || string.Equals(actualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase)) { strictArgs = " -strict -2"; } @@ -1755,10 +1756,9 @@ public class DynamicHlsController : BaseJellyfinApiController } var bitrate = state.OutputAudioBitrate; - - if (bitrate.HasValue) + if (bitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(actualOutputAudioCodec, StringComparison.OrdinalIgnoreCase)) { - var vbrParam = _encodingHelper.GetAudioVbrModeParam(state.OutputAudioCodec, bitrate.Value / (channels ?? 2)); + var vbrParam = _encodingHelper.GetAudioVbrModeParam(actualOutputAudioCodec, bitrate.Value / (channels ?? 2)); if (_encodingOptions.EnableAudioVbr && vbrParam is not null) { args += vbrParam; diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index 555babc0e..4486954c6 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Extensions; using Jellyfin.Api.Models.StreamingDtos; +using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; @@ -223,9 +224,17 @@ public class DynamicHlsHelper sdrVideoUrl += "&AllowVideoStreamCopy=false"; var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec); - var sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream, state.OutputAudioChannels) ?? 0; - var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate; + var sdrOutputAudioBitrate = 0; + if (EncodingHelper.LosslessAudioCodecs.Contains(state.VideoRequest.AudioCodec, StringComparison.OrdinalIgnoreCase)) + { + sdrOutputAudioBitrate = state.AudioStream.BitRate ?? 0; + } + else + { + sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream, state.OutputAudioChannels) ?? 0; + } + var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate; var sdrPlaylist = AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup); // Provide a workaround for the case issue between flac and fLaC. diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 6ce98d231..9c91dcc6f 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -181,12 +181,18 @@ public static class StreamingHelpers : GetOutputFileExtension(state, mediaSource); } - state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.'); - - state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingRequest.AudioCodec, state.AudioStream, state.OutputAudioChannels); - - state.OutputAudioCodec = streamingRequest.AudioCodec; + var outputAudioCodec = streamingRequest.AudioCodec; + if (EncodingHelper.LosslessAudioCodecs.Contains(outputAudioCodec)) + { + state.OutputAudioBitrate = state.AudioStream.BitRate ?? 0; + } + else + { + state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingRequest.AudioCodec, state.AudioStream, state.OutputAudioChannels) ?? 0; + } + state.OutputAudioCodec = outputAudioCodec; + state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.'); state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec); if (state.VideoRequest is not null) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5f38ddbba..b6118e600 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -86,6 +86,16 @@ namespace MediaBrowser.Controller.MediaEncoding { "truehd", 6 }, }; + public static readonly string[] LosslessAudioCodecs = new string[] + { + "alac", + "ape", + "flac", + "mlp", + "truehd", + "wavpack" + }; + public EncodingHelper( IApplicationPaths appPaths, IMediaEncoder mediaEncoder, @@ -2149,17 +2159,6 @@ namespace MediaBrowser.Controller.MediaEncoding }; } - if (string.Equals(audioCodec, "flac", StringComparison.OrdinalIgnoreCase) - || string.Equals(audioCodec, "alac", StringComparison.OrdinalIgnoreCase)) - { - if (inputChannels >= 6) - { - return Math.Min(3584000, bitrate); - } - - return Math.Min(1536000, bitrate); - } - if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase) || string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase)) { @@ -2172,20 +2171,10 @@ namespace MediaBrowser.Controller.MediaEncoding }; } - if (string.Equals(audioCodec, "truehd", StringComparison.OrdinalIgnoreCase)) - { - return (inputChannels, outputChannels) switch - { - (> 0, > 0) => Math.Min(outputChannels * 768000, bitrate), - (> 0, _) => Math.Min(inputChannels * 768000, bitrate), - (_, _) => Math.Min(768000, bitrate) - }; - } - // Empty bitrate area is not allow on iOS // Default audio bitrate to 128K per channel if we don't have codec specific defaults // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options - return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 1); + return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2); } public string GetAudioVbrModeParam(string encoder, int bitratePerChannel) @@ -5867,8 +5856,7 @@ namespace MediaBrowser.Controller.MediaEncoding } var bitrate = state.OutputAudioBitrate; - - if (bitrate.HasValue) + if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase)) { var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value / (channels ?? 2)); if (encodingOptions.EnableAudioVbr && vbrParam is not null) @@ -5897,10 +5885,11 @@ namespace MediaBrowser.Controller.MediaEncoding var bitrate = state.OutputAudioBitrate; var channels = state.OutputAudioChannels; + var outputCodec = state.OutputAudioCodec; - if (bitrate.HasValue) + if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase)) { - var vbrParam = GetAudioVbrModeParam(state.OutputAudioCodec, bitrate.Value / (channels ?? 2)); + var vbrParam = GetAudioVbrModeParam(outputCodec, bitrate.Value / (channels ?? 2)); if (encodingOptions.EnableAudioVbr && vbrParam is not null) { audioTranscodeParams.Add(vbrParam); @@ -5916,12 +5905,12 @@ namespace MediaBrowser.Controller.MediaEncoding audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)); } - if (!string.IsNullOrEmpty(state.OutputAudioCodec)) + if (!string.IsNullOrEmpty(outputCodec)) { audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state)); } - if (!string.Equals(state.OutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase)) { // opus only supports specific sampling rates var sampleRate = state.OutputAudioSampleRate; -- cgit v1.2.3 From e0a7e9baa06fb8efb06aa91fc4d1cd205d0b7d40 Mon Sep 17 00:00:00 2001 From: knackebrot Date: Tue, 21 Mar 2023 15:01:32 +0100 Subject: Fix audio VBR calculation Pass encoder, not codec --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 4 ++-- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'Jellyfin.Api/Controllers/DynamicHlsController.cs') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index b827daa3a..42a7ac2b1 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1693,7 +1693,7 @@ public class DynamicHlsController : BaseJellyfinApiController if (audioBitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(state.ActualOutputAudioCodec, StringComparison.OrdinalIgnoreCase)) { - var vbrParam = _encodingHelper.GetAudioVbrModeParam(state.OutputAudioCodec, audioBitrate.Value / (audioChannels ?? 2)); + var vbrParam = _encodingHelper.GetAudioVbrModeParam(audioCodec, audioBitrate.Value / (audioChannels ?? 2)); if (_encodingOptions.EnableAudioVbr && vbrParam is not null) { audioTranscodeParams += vbrParam; @@ -1760,7 +1760,7 @@ public class DynamicHlsController : BaseJellyfinApiController var bitrate = state.OutputAudioBitrate; if (bitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(actualOutputAudioCodec, StringComparison.OrdinalIgnoreCase)) { - var vbrParam = _encodingHelper.GetAudioVbrModeParam(actualOutputAudioCodec, bitrate.Value / (channels ?? 2)); + var vbrParam = _encodingHelper.GetAudioVbrModeParam(audioCodec, bitrate.Value / (channels ?? 2)); if (_encodingOptions.EnableAudioVbr && vbrParam is not null) { args += vbrParam; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 960b0b7cd..f7ee3c173 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -6015,7 +6015,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase)) { - var vbrParam = GetAudioVbrModeParam(outputCodec, bitrate.Value / (channels ?? 2)); + var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value / (channels ?? 2)); if (encodingOptions.EnableAudioVbr && vbrParam is not null) { audioTranscodeParams.Add(vbrParam); -- cgit v1.2.3 From e74630a6137b77e76b7fc311859e6c81bcd776b5 Mon Sep 17 00:00:00 2001 From: Stepan Goremykin Date: Sat, 1 Apr 2023 23:00:51 +0200 Subject: Use MinBy and MaxBy --- Emby.Server.Implementations/Plugins/PluginManager.cs | 2 +- Emby.Server.Implementations/Session/WebSocketController.cs | 4 +--- Jellyfin.Api/Controllers/DynamicHlsController.cs | 4 +--- Jellyfin.Api/Controllers/PluginsController.cs | 2 +- Jellyfin.Networking/Manager/NetworkManager.cs | 3 +-- 5 files changed, 5 insertions(+), 10 deletions(-) (limited to 'Jellyfin.Api/Controllers/DynamicHlsController.cs') diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 7c23254a1..f14f87ccd 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -304,7 +304,7 @@ namespace Emby.Server.Implementations.Plugins // If no version is given, return the current instance. var plugins = _plugins.Where(p => p.Id.Equals(id)).ToList(); - plugin = plugins.FirstOrDefault(p => p.Instance is not null) ?? plugins.OrderByDescending(p => p.Version).FirstOrDefault(); + plugin = plugins.FirstOrDefault(p => p.Instance is not null) ?? plugins.MaxBy(p => p.Version); } else { diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index 051fa5b3c..cdc736950 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -69,9 +69,7 @@ namespace Emby.Server.Implementations.Session T data, CancellationToken cancellationToken) { - var socket = GetActiveSockets() - .OrderByDescending(i => i.LastActivityDate) - .FirstOrDefault(); + var socket = GetActiveSockets().MaxBy(i => i.LastActivityDate); if (socket is null) { diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 42a7ac2b1..09cf7303e 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -20,7 +20,6 @@ using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; using MediaBrowser.MediaEncoding.Encoder; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; @@ -2030,8 +2029,7 @@ public class DynamicHlsController : BaseJellyfinApiController { return fileSystem.GetFiles(folder, new[] { segmentExtension }, true, false) .Where(i => Path.GetFileNameWithoutExtension(i.Name).StartsWith(filePrefix, StringComparison.OrdinalIgnoreCase)) - .OrderByDescending(fileSystem.GetLastWriteTimeUtc) - .FirstOrDefault(); + .MaxBy(fileSystem.GetLastWriteTimeUtc); } catch (IOException) { diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 4726cf066..72ad14a28 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -146,7 +146,7 @@ public class PluginsController : BaseJellyfinApiController var plugins = _pluginManager.Plugins.Where(p => p.Id.Equals(pluginId)).ToList(); // Select the un-instanced one first. - var plugin = plugins.FirstOrDefault(p => p.Instance is null) ?? plugins.OrderBy(p => p.Manifest.Status).FirstOrDefault(); + var plugin = plugins.FirstOrDefault(p => p.Instance is null) ?? plugins.MinBy(p => p.Manifest.Status); if (plugin is not null) { diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index f406e27a6..ac9258ad1 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -1256,8 +1256,7 @@ namespace Jellyfin.Networking.Manager // Look for the best internal address. bindAddress = addresses .Where(p => IsInLocalNetwork(p) && (p.Contains(source) || p.Equals(IPAddress.None))) - .OrderBy(p => p.Tag) - .FirstOrDefault()?.Address; + .MinBy(p => p.Tag)?.Address; } if (bindAddress is not null) -- cgit v1.2.3