diff options
59 files changed, 429 insertions, 308 deletions
diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml index 19d65ea0c1..926d1d3224 100644 --- a/.ci/azure-pipelines-package.yml +++ b/.ci/azure-pipelines-package.yml @@ -26,6 +26,8 @@ jobs: BuildConfiguration: linux.amd64-musl Linux.arm64: BuildConfiguration: linux.arm64 + Linux.musl-linux-arm64: + BuildConfiguration: linux.musl-linux-arm64 Linux.armhf: BuildConfiguration: linux.armhf Windows.amd64: diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index a7d2b2e409..9c08929e78 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -157,6 +157,7 @@ - [jonas-resch](https://github.com/jonas-resch) - [vgambier](https://github.com/vgambier) - [MinecraftPlaye](https://github.com/MinecraftPlaye) + - [RealGreenDragon](https://github.com/RealGreenDragon) # Emby Contributors diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index df6539a5a6..8e3a335c66 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -446,7 +446,7 @@ namespace Emby.Dlna.Didl /// </summary> /// <remarks> /// If context is a season, this will return a string containing just episode number and name. - /// Otherwise the result will include series nams and season number. + /// Otherwise the result will include series names and season number. /// </remarks> /// <param name="episode">The episode.</param> /// <param name="context">Current context.</param> diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index fe78d74ee7..74624334bb 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -123,7 +123,7 @@ namespace Emby.Dlna /// <summary> /// Attempts to match a device with a profile. /// Rules: - /// - If the profile field has no value, the field matches irregardless of its contents. + /// - If the profile field has no value, the field matches regardless of its contents. /// - the profile field can be an exact match, or a reg exp. /// </summary> /// <param name="deviceInfo">The <see cref="DeviceIdentification"/> of the device.</param> diff --git a/Emby.Dlna/IDlnaEventManager.cs b/Emby.Dlna/IDlnaEventManager.cs index 33cf0896ba..eea030d6d1 100644 --- a/Emby.Dlna/IDlnaEventManager.cs +++ b/Emby.Dlna/IDlnaEventManager.cs @@ -16,7 +16,7 @@ namespace Emby.Dlna /// </summary> /// <param name="subscriptionId">The subscription identifier.</param> /// <param name="notificationType">The notification type.</param> - /// <param name="requestedTimeoutString">The requested timeout as a sting.</param> + /// <param name="requestedTimeoutString">The requested timeout as a string.</param> /// <param name="callbackUrl">The callback url.</param> /// <returns>The response.</returns> EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl); @@ -25,7 +25,7 @@ namespace Emby.Dlna /// Creates the event subscription. /// </summary> /// <param name="notificationType">The notification type.</param> - /// <param name="requestedTimeoutString">The requested timeout as a sting.</param> + /// <param name="requestedTimeoutString">The requested timeout as a string.</param> /// <param name="callbackUrl">The callback url.</param> /// <returns>The response.</returns> EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl); diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index 8eb90f4455..0b480f5ab0 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -235,7 +235,7 @@ namespace Emby.Dlna.PlayTo _logger.LogDebug("Setting mute"); var value = mute ? 1 : 0; - await new SsdpHttpClient(_httpClientFactory) + await new DlnaHttpClient(_logger, _httpClientFactory) .SendCommandAsync( Properties.BaseUrl, service, @@ -276,7 +276,7 @@ namespace Emby.Dlna.PlayTo // Remote control will perform better Volume = value; - await new SsdpHttpClient(_httpClientFactory) + await new DlnaHttpClient(_logger, _httpClientFactory) .SendCommandAsync( Properties.BaseUrl, service, @@ -303,7 +303,7 @@ namespace Emby.Dlna.PlayTo throw new InvalidOperationException("Unable to find service"); } - await new SsdpHttpClient(_httpClientFactory) + await new DlnaHttpClient(_logger, _httpClientFactory) .SendCommandAsync( Properties.BaseUrl, service, @@ -343,7 +343,7 @@ namespace Emby.Dlna.PlayTo } var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary); - await new SsdpHttpClient(_httpClientFactory) + await new DlnaHttpClient(_logger, _httpClientFactory) .SendCommandAsync( Properties.BaseUrl, service, @@ -400,7 +400,8 @@ namespace Emby.Dlna.PlayTo } var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary); - await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header, cancellationToken) + await new DlnaHttpClient(_logger, _httpClientFactory) + .SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header, cancellationToken) .ConfigureAwait(false); } @@ -428,7 +429,7 @@ namespace Emby.Dlna.PlayTo throw new InvalidOperationException("Unable to find service"); } - return new SsdpHttpClient(_httpClientFactory).SendCommandAsync( + return new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync( Properties.BaseUrl, service, command.Name, @@ -461,7 +462,7 @@ namespace Emby.Dlna.PlayTo var service = GetAvTransportService(); - await new SsdpHttpClient(_httpClientFactory) + await new DlnaHttpClient(_logger, _httpClientFactory) .SendCommandAsync( Properties.BaseUrl, service, @@ -485,7 +486,7 @@ namespace Emby.Dlna.PlayTo var service = GetAvTransportService(); - await new SsdpHttpClient(_httpClientFactory) + await new DlnaHttpClient(_logger, _httpClientFactory) .SendCommandAsync( Properties.BaseUrl, service, @@ -618,7 +619,7 @@ namespace Emby.Dlna.PlayTo return; } - var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync( + var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync( Properties.BaseUrl, service, command.Name, @@ -668,7 +669,7 @@ namespace Emby.Dlna.PlayTo return; } - var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync( + var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync( Properties.BaseUrl, service, command.Name, @@ -701,7 +702,7 @@ namespace Emby.Dlna.PlayTo return null; } - var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync( + var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync( Properties.BaseUrl, service, command.Name, @@ -747,7 +748,7 @@ namespace Emby.Dlna.PlayTo return null; } - var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync( + var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync( Properties.BaseUrl, service, command.Name, @@ -819,7 +820,7 @@ namespace Emby.Dlna.PlayTo return (false, null); } - var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync( + var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync( Properties.BaseUrl, service, command.Name, @@ -997,7 +998,7 @@ namespace Emby.Dlna.PlayTo string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl); - var httpClient = new SsdpHttpClient(_httpClientFactory); + var httpClient = new DlnaHttpClient(_logger, _httpClientFactory); var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); if (document == null) @@ -1029,7 +1030,7 @@ namespace Emby.Dlna.PlayTo string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl); - var httpClient = new SsdpHttpClient(_httpClientFactory); + var httpClient = new DlnaHttpClient(_logger, _httpClientFactory); _logger.LogDebug("Dlna Device.GetRenderingProtocolAsync"); var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); if (document == null) @@ -1064,7 +1065,7 @@ namespace Emby.Dlna.PlayTo public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClientFactory httpClientFactory, ILogger logger, CancellationToken cancellationToken) { - var ssdpHttpClient = new SsdpHttpClient(httpClientFactory); + var ssdpHttpClient = new DlnaHttpClient(logger, httpClientFactory); var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false); if (document == null) diff --git a/Emby.Dlna/PlayTo/DlnaHttpClient.cs b/Emby.Dlna/PlayTo/DlnaHttpClient.cs new file mode 100644 index 0000000000..75ff542dd8 --- /dev/null +++ b/Emby.Dlna/PlayTo/DlnaHttpClient.cs @@ -0,0 +1,108 @@ +#pragma warning disable CS1591 + +using System; +using System.Globalization; +using System.Net.Http; +using System.Net.Mime; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Linq; +using Emby.Dlna.Common; +using MediaBrowser.Common.Net; +using Microsoft.Extensions.Logging; + +namespace Emby.Dlna.PlayTo +{ + public class DlnaHttpClient + { + private readonly ILogger _logger; + private readonly IHttpClientFactory _httpClientFactory; + + public DlnaHttpClient(ILogger logger, IHttpClientFactory httpClientFactory) + { + _logger = logger; + _httpClientFactory = httpClientFactory; + } + + private static string NormalizeServiceUrl(string baseUrl, string serviceUrl) + { + // If it's already a complete url, don't stick anything onto the front of it + if (serviceUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase)) + { + return serviceUrl; + } + + if (!serviceUrl.StartsWith('/')) + { + serviceUrl = "/" + serviceUrl; + } + + return baseUrl + serviceUrl; + } + + private async Task<XDocument?> SendRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + using var response = await _httpClientFactory.CreateClient(NamedClient.Dlna).SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + try + { + return await XDocument.LoadAsync( + stream, + LoadOptions.None, + cancellationToken).ConfigureAwait(false); + } + catch (XmlException ex) + { + _logger.LogError(ex, "Failed to parse response"); + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Malformed response: {Content}\n", await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false)); + } + + return null; + } + } + + public async Task<XDocument?> GetDataAsync(string url, CancellationToken cancellationToken) + { + using var request = new HttpRequestMessage(HttpMethod.Get, url); + + // Have to await here instead of returning the Task directly, otherwise request would be disposed too soon + return await SendRequestAsync(request, cancellationToken).ConfigureAwait(false); + } + + public async Task<XDocument?> SendCommandAsync( + string baseUrl, + DeviceService service, + string command, + string postData, + string? header = null, + CancellationToken cancellationToken = default) + { + using var request = new HttpRequestMessage(HttpMethod.Post, NormalizeServiceUrl(baseUrl, service.ControlUrl)) + { + Content = new StringContent(postData, Encoding.UTF8, MediaTypeNames.Text.Xml) + }; + + request.Headers.TryAddWithoutValidation( + "SOAPACTION", + string.Format( + CultureInfo.InvariantCulture, + "\"{0}#{1}\"", + service.ServiceType, + command)); + request.Headers.Pragma.ParseAdd("no-cache"); + + if (!string.IsNullOrEmpty(header)) + { + request.Headers.TryAddWithoutValidation("contentFeatures.dlna.org", header); + } + + // Have to await here instead of returning the Task directly, otherwise request would be disposed too soon + return await SendRequestAsync(request, cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs deleted file mode 100644 index cade7b4c2c..0000000000 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ /dev/null @@ -1,141 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System; -using System.Globalization; -using System.Net.Http; -using System.Net.Mime; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml.Linq; -using Emby.Dlna.Common; -using MediaBrowser.Common.Net; - -namespace Emby.Dlna.PlayTo -{ - public class SsdpHttpClient - { - private const string USERAGENT = "Microsoft-Windows/6.2 UPnP/1.0 Microsoft-DLNA DLNADOC/1.50"; - private const string FriendlyName = "Jellyfin"; - - private readonly IHttpClientFactory _httpClientFactory; - - public SsdpHttpClient(IHttpClientFactory httpClientFactory) - { - _httpClientFactory = httpClientFactory; - } - - public async Task<XDocument> SendCommandAsync( - string baseUrl, - DeviceService service, - string command, - string postData, - string header = null, - CancellationToken cancellationToken = default) - { - var url = NormalizeServiceUrl(baseUrl, service.ControlUrl); - using var response = await PostSoapDataAsync( - url, - $"\"{service.ServiceType}#{command}\"", - postData, - header, - cancellationToken) - .ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - - await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - return await XDocument.LoadAsync( - stream, - LoadOptions.None, - cancellationToken).ConfigureAwait(false); - } - - private static string NormalizeServiceUrl(string baseUrl, string serviceUrl) - { - // If it's already a complete url, don't stick anything onto the front of it - if (serviceUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase)) - { - return serviceUrl; - } - - if (!serviceUrl.StartsWith('/')) - { - serviceUrl = "/" + serviceUrl; - } - - return baseUrl + serviceUrl; - } - - public async Task SubscribeAsync( - string url, - string ip, - int port, - string localIp, - int eventport, - int timeOut = 3600) - { - using var options = new HttpRequestMessage(new HttpMethod("SUBSCRIBE"), url); - options.Headers.UserAgent.ParseAdd(USERAGENT); - options.Headers.TryAddWithoutValidation("HOST", ip + ":" + port.ToString(CultureInfo.InvariantCulture)); - options.Headers.TryAddWithoutValidation("CALLBACK", "<" + localIp + ":" + eventport.ToString(CultureInfo.InvariantCulture) + ">"); - options.Headers.TryAddWithoutValidation("NT", "upnp:event"); - options.Headers.TryAddWithoutValidation("TIMEOUT", "Second-" + timeOut.ToString(CultureInfo.InvariantCulture)); - - using var response = await _httpClientFactory.CreateClient(NamedClient.Default) - .SendAsync(options, HttpCompletionOption.ResponseHeadersRead) - .ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - } - - public async Task<XDocument> GetDataAsync(string url, CancellationToken cancellationToken) - { - using var options = new HttpRequestMessage(HttpMethod.Get, url); - options.Headers.UserAgent.ParseAdd(USERAGENT); - options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName); - using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - try - { - return await XDocument.LoadAsync( - stream, - LoadOptions.None, - cancellationToken).ConfigureAwait(false); - } - catch - { - return null; - } - } - - private async Task<HttpResponseMessage> PostSoapDataAsync( - string url, - string soapAction, - string postData, - string header, - CancellationToken cancellationToken) - { - if (soapAction[0] != '\"') - { - soapAction = $"\"{soapAction}\""; - } - - using var options = new HttpRequestMessage(HttpMethod.Post, url); - options.Headers.UserAgent.ParseAdd(USERAGENT); - options.Headers.TryAddWithoutValidation("SOAPACTION", soapAction); - options.Headers.TryAddWithoutValidation("Pragma", "no-cache"); - options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName); - - if (!string.IsNullOrEmpty(header)) - { - options.Headers.TryAddWithoutValidation("contentFeatures.dlna.org", header); - } - - options.Content = new StringContent(postData, Encoding.UTF8, MediaTypeNames.Text.Xml); - - return await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - } - } -} diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 2843fb8f83..6796845525 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2529,7 +2529,7 @@ namespace Emby.Server.Implementations.Library } catch (Exception ex) { - _logger.LogError(ex, "Error reading the episode informations with ffprobe. Episode: {EpisodeInfo}", episodeInfo.Path); + _logger.LogError(ex, "Error reading the episode information with ffprobe. Episode: {EpisodeInfo}", episodeInfo.Path); } var changed = false; diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index a60251dacd..b2f388a667 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -387,7 +387,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies if (!string.IsNullOrEmpty(item.Path)) { - // check for imdb id - we use full media path, as we can assume, that this will match in any use case (wither id in parent dir or in file name) + // check for imdb id - we use full media path, as we can assume, that this will match in any use case (either id in parent dir or in file name) var imdbid = item.Path.AsSpan().GetAttributeValue("imdbid"); if (!string.IsNullOrWhiteSpace(imdbid)) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 2a468e14dc..bcb42e1626 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -196,7 +196,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts IsInfiniteStream = true, IsRemote = isRemote, - IgnoreDts = true, + IgnoreDts = info.IgnoreDts, SupportsDirectPlay = supportsDirectPlay, SupportsDirectStream = supportsDirectStream, diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 708ff52d79..be06356a4f 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -199,7 +199,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts if (string.IsNullOrWhiteSpace(numberString)) { // Using this as a fallback now as this leads to Problems with channels like "5 USA" - // where 5 isn't ment to be the channel number + // where 5 isn't meant to be the channel number // Check for channel number with the format from SatIp // #EXTINF:0,84. VOX Schweiz // #EXTINF:0,84.0 - VOX Schweiz diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json index 50d019f906..186ec44d24 100644 --- a/Emby.Server.Implementations/Localization/Core/ko.json +++ b/Emby.Server.Implementations/Localization/Core/ko.json @@ -120,5 +120,8 @@ "Forced": "강제하기", "Default": "기본 설정", "TaskOptimizeDatabaseDescription": "데이터베이스를 압축하고 사용 가능한 공간을 늘립니다. 라이브러리를 검색한 후 이 작업을 실행하거나 데이터베이스 수정같은 비슷한 작업을 수행하면 성능이 향상될 수 있습니다.", - "TaskOptimizeDatabase": "데이터베이스 최적화" + "TaskOptimizeDatabase": "데이터베이스 최적화", + "TaskKeyframeExtractorDescription": "비디오 파일에서 키프레임을 추출하여 더 정확한 HLS 재생 목록을 만듭니다. 이 작업은 오랫동안 진행될 수 있습니다.", + "TaskKeyframeExtractor": "키프레임 추출", + "External": "외부" } diff --git a/Emby.Server.Implementations/Localization/Core/my.json b/Emby.Server.Implementations/Localization/Core/my.json index 2642373fa9..198f7540c8 100644 --- a/Emby.Server.Implementations/Localization/Core/my.json +++ b/Emby.Server.Implementations/Localization/Core/my.json @@ -6,97 +6,97 @@ "Artists": "အနုပညာရှင်များ", "Albums": "သီချင်းအခွေများ", "TaskOptimizeDatabaseDescription": "ဒေတာဘေ့စ်ကို ကျစ်လစ်စေပြီး နေရာလွတ်များကို ဖြတ်တောက်ပေးသည်။ စာကြည့်တိုက်ကို စကင်န်ဖတ်ပြီးနောက် ဤလုပ်ငန်းကို လုပ်ဆောင်ခြင်း သို့မဟုတ် ဒေတာဘေ့စ်မွမ်းမံမှုများ စွမ်းဆောင်ရည်ကို မြှင့်တင်ပေးနိုင်သည်ဟု ရည်ညွှန်းသော အခြားပြောင်းလဲမှုများကို လုပ်ဆောင်ခြင်း။.", - "TaskOptimizeDatabase": "ဒေတာဘေ့စ်ကို အကောင်းဆုံးဖြစ်အောင်လုပ်ပါ။", + "TaskOptimizeDatabase": "ဒေတာဘေ့စ်ကို အကောင်းဆုံးဖြစ်အောင်လုပ်ပါ", "TaskDownloadMissingSubtitlesDescription": "မက်တာဒေတာ ဖွဲ့စည်းမှုပုံစံအပေါ် အခြေခံ၍ ပျောက်ဆုံးနေသော စာတန်းထိုးများအတွက် အင်တာနက်ကို ရှာဖွေသည်။", - "TaskDownloadMissingSubtitles": "ပျောက်ဆုံးနေသော စာတန်းထိုးများကို ဒေါင်းလုဒ်လုပ်ပါ။", + "TaskDownloadMissingSubtitles": "ပျောက်ဆုံးနေသော စာတန်းထိုးများကို ဒေါင်းလုဒ်လုပ်ပါ", "TaskRefreshChannelsDescription": "အင်တာနက်ချန်နယ်အချက်အလက်ကို ပြန်လည်စတင်သည်။", - "TaskRefreshChannels": "ချန်နယ်များကို ပြန်လည်စတင်ပါ။", + "TaskRefreshChannels": "ချန်နယ်များကို ပြန်လည်စတင်ပါ", "TaskCleanTranscodeDescription": "သက်တမ်း တစ်ရက်ထက်ပိုသော အသွင်ပြောင်းကုဒ်ဖိုင်များကို ဖျက်ပါ။", - "TaskCleanTranscode": "Transcode လမ်းညွှန်ကို သန့်ရှင်းပါ။", + "TaskCleanTranscode": "Transcode လမ်းညွှန်ကို သန့်ရှင်းပါ", "TaskUpdatePluginsDescription": "အလိုအလျောက် အပ်ဒိတ်လုပ်ရန် စီစဉ်ထားသော ပလပ်အင်များအတွက် အပ်ဒိတ်များကို ဒေါင်းလုဒ်လုပ်ပြီး ထည့်သွင်းပါ။", - "TaskUpdatePlugins": "ပလပ်အင်များကို အပ်ဒိတ်လုပ်ပါ။", + "TaskUpdatePlugins": "ပလပ်အင်များကို အပ်ဒိတ်လုပ်ပါ", "TaskRefreshPeopleDescription": "သင့်မီဒီယာစာကြည့်တိုက်ရှိ သရုပ်ဆောင်များနှင့် ဒါရိုက်တာများအတွက် မက်တာဒေတာကို အပ်ဒိတ်လုပ်ပါ။", - "TaskRefreshPeople": "လူများကို ပြန်လည်ဆန်းသစ်ပါ။", + "TaskRefreshPeople": "လူများကို ပြန်လည်ဆန်းသစ်ပါ", "TaskCleanLogsDescription": "{0} ရက်ထက်ပိုသော မှတ်တမ်းဖိုင်များကို ဖျက်သည်။", - "TaskCleanLogs": "မှတ်တမ်းလမ်းညွှန်ကို သန့်ရှင်းပါ။", + "TaskCleanLogs": "မှတ်တမ်းလမ်းညွှန်ကို သန့်ရှင်းပါ", "TaskRefreshLibraryDescription": "သင့်မီဒီယာဒစ်ဂျစ်တိုက်ကို ဖိုင်အသစ်များရှိမရှိ စကင်န်ဖတ်ပြီး ဖိုင်ရဲ့အကြောင်းအရာများ ကို ပြန်ပြုပြင်မွမ်းမံပါ။", - "TaskRefreshLibrary": "မီဒီယာစာကြည့်တိုက်ကို စကင်န်ဖတ်ပါ။", + "TaskRefreshLibrary": "မီဒီယာစာကြည့်တိုက်ကို စကင်န်ဖတ်ပါ", "TaskRefreshChapterImagesDescription": "အခန်းများပါရှိသော ဗီဒီယိုများအတွက် ပုံသေးများကို ဖန်တီးပါ။", - "TaskRefreshChapterImages": "အခန်းတစ်ခုစီ ပုံများကို ထုတ်ယူပါ။", + "TaskRefreshChapterImages": "အခန်းတစ်ခုစီ ပုံများကို ထုတ်ယူပါ", "TaskCleanCacheDescription": "စနစ်မှ မလိုအပ်တော့သော ကက်ရှ်ဖိုင်များကို ဖျက်ပါ။.", - "TaskCleanCache": "Cache Directory ကို ရှင်းပါ။", + "TaskCleanCache": "Cache Directory ကို ရှင်းပါ", "TaskCleanActivityLogDescription": "စီစဉ်သတ်မှတ်ထားသော အသက်ထက် ပိုကြီးသော လုပ်ဆောင်ချက်မှတ်တမ်းများကို ဖျက်ပါ။", - "TaskCleanActivityLog": "လုပ်ဆောင်ချက်မှတ်တမ်းကို ရှင်းလင်းပါ။", + "TaskCleanActivityLog": "လုပ်ဆောင်ချက်မှတ်တမ်းကို ရှင်းလင်းပါ", "TasksChannelsCategory": "အင်တာနက် ချန်နယ်လိုင်းများ", "TasksApplicationCategory": "အပလီကေးရှင်း", "TasksLibraryCategory": "မီဒီယာတိုက်", "TasksMaintenanceCategory": "ပြုပြင် ထိန်းသိမ်းခြင်း", "VersionNumber": "ဗားရှင်း {0}", "ValueSpecialEpisodeName": "အထူး- {0}", - "ValueHasBeenAddedToLibrary": "{0} ကို သင့်မီဒီယာဒစ်ဂျစ်တိုက်သို့ ပေါင်းထည့်လိုက်ပါပြီ။", + "ValueHasBeenAddedToLibrary": "{0} ကို သင့်မီဒီယာဒစ်ဂျစ်တိုက်သို့ ပေါင်းထည့်လိုက်ပါပြီ", "UserStoppedPlayingItemWithValues": "{0} သည် {1} ကို {2} တွင် ဖွင့်ပြီးပါပြီ", "UserStartedPlayingItemWithValues": "{0} သည် {1} ကို {2} တွင် ပြသနေသည်", "UserPolicyUpdatedWithName": "{0} အတွက် အသုံးပြုသူမူဝါဒကို အပ်ဒိတ်လုပ်ပြီးပါပြီ", "UserPasswordChangedWithName": "အသုံးပြုသူ {0} အတွက် စကားဝှက်ကို ပြောင်းထားသည်", "UserOnlineFromDevice": "{0} သည် {1} မှ အွန်လိုင်းဖြစ်သည်", "UserOfflineFromDevice": "{0} သည် {1} မှ ချိတ်ဆက်မှုပြတ်တောက်သွားသည်", - "UserLockedOutWithName": "အသုံးပြုသူ {0} အား လော့ခ်ချထားသည်။", + "UserLockedOutWithName": "အသုံးပြုသူ {0} အား လော့ခ်ချထားသည်", "UserDownloadingItemWithValues": "{0} သည် {1} ကို ဒေါင်းလုဒ်လုပ်နေသည်", - "UserDeletedWithName": "အသုံးပြုသူ {0} ကို ဖျက်လိုက်ပါပြီ။", - "UserCreatedWithName": "အသုံးပြုသူ {0} ကို ဖန်တီးပြီးပါပြီ။", + "UserDeletedWithName": "အသုံးပြုသူ {0} ကို ဖျက်လိုက်ပါပြီ", + "UserCreatedWithName": "အသုံးပြုသူ {0} ကို ဖန်တီးပြီးပါပြီ", "User": "အသုံးပြုသူ", "Undefined": "သတ်မှတ်မထားသော", "TvShows": "တီဗီ ဇာတ်လမ်းတွဲများ", "System": "စနစ်", "Sync": "ထပ်တူကျသည်။", - "SubtitleDownloadFailureFromForItem": "{1} အတွက် {0} မှ စာတန်းထိုးများ ဒေါင်းလုဒ်လုပ်ခြင်း မအောင်မြင်ပါ။", + "SubtitleDownloadFailureFromForItem": "{1} အတွက် {0} မှ စာတန်းထိုးများ ဒေါင်းလုဒ်လုပ်ခြင်း မအောင်မြင်ပါ", "StartupEmbyServerIsLoading": "Jellyfin ဆာဗာကို အသင့်ပြင်နေပါသည်။ ခဏနေ ထပ်စမ်းကြည့်ပါ။", "Songs": "သီချင်းများ", "Shows": "ဇာတ်လမ်းတွဲများ", - "ServerNameNeedsToBeRestarted": "{0} ကို ပြန်လည်စတင်ရန် လိုအပ်သည်။", - "ScheduledTaskStartedWithName": "{0} စတင်ခဲ့သည်။", - "ScheduledTaskFailedWithName": "{0} မအောင်မြင်ပါ။", + "ServerNameNeedsToBeRestarted": "{0} ကို ပြန်လည်စတင်ရန် လိုအပ်သည်", + "ScheduledTaskStartedWithName": "{0} စတင်ခဲ့သည်", + "ScheduledTaskFailedWithName": "{0} မအောင်မြင်ပါ", "ProviderValue": "ဝန်ဆောင်မှုပေးသူ- {0}", - "PluginUpdatedWithName": "ပလပ်ခ်အင် {0} ကို အပ်ဒိတ်လုပ်ထားသည်။", - "PluginUninstalledWithName": "ပလပ်ခ်အင် {0} ကို ဖြုတ်လိုက်ပါပြီ။", - "PluginInstalledWithName": "ပလပ်ခ်အင် {0} ကို ထည့်သွင်းခဲ့သည်။", + "PluginUpdatedWithName": "ပလပ်ခ်အင် {0} ကို အပ်ဒိတ်လုပ်ထားသည်", + "PluginUninstalledWithName": "ပလပ်ခ်အင် {0} ကို ဖြုတ်လိုက်ပါပြီ", + "PluginInstalledWithName": "ပလပ်ခ်အင် {0} ကို ထည့်သွင်းခဲ့သည်", "Plugin": "ပလပ်အင်", "Playlists": "အစီအစဉ်များ", "Photos": "ဓာတ်ပုံများ", - "NotificationOptionVideoPlaybackStopped": "ဗီဒီယိုဖွင့်ခြင်း ရပ်သွားသည်။", - "NotificationOptionVideoPlayback": "ဗီဒီယိုဖွင့်ခြင်း စတင်ပါပြီ။", - "NotificationOptionUserLockedOut": "အသုံးပြုသူ ဝင်ရန် တားမြစ်ခံရသည်။", + "NotificationOptionVideoPlaybackStopped": "ဗီဒီယိုဖွင့်ခြင်း ရပ်သွားသည်", + "NotificationOptionVideoPlayback": "ဗီဒီယိုဖွင့်ခြင်း စတင်ပါပြီ", + "NotificationOptionUserLockedOut": "အသုံးပြုသူ ဝင်ရန် တားမြစ်ခံရသည်", "NotificationOptionTaskFailed": "စီစဉ်ထားသော အလုပ်ပျက်ကွက်", - "NotificationOptionServerRestartRequired": "ဆာဗာ ပြန်လည်စတင်ရန် လိုအပ်သည်။", - "NotificationOptionPluginUpdateInstalled": "ပလပ်အင် အပ်ဒိတ် ထည့်သွင်းပြီးပါပြီ။", - "NotificationOptionPluginUninstalled": "ပလပ်အင်ကို ဖြုတ်လိုက်ပါပြီ။", - "NotificationOptionPluginInstalled": "ပလပ်အင် ထည့်သွင်းထားသည်။", - "NotificationOptionPluginError": "ပလပ်အင် ချို့ယွင်းခြင်း။", - "NotificationOptionNewLibraryContent": "အသစ်များ ထပ်ထည့်ထားပါတယ်။", - "NotificationOptionInstallationFailed": "ထည့်သွင်းမှု မအောင်မြင်ပါ။", - "NotificationOptionCameraImageUploaded": "ကင်မရာမှ ဓာတ်ပုံ အပ်လုဒ် ပြီးပါပြီ။", - "NotificationOptionAudioPlaybackStopped": "အသံဖိုင်ဖွင့်ခြင်း ရပ်သွားသည်။", - "NotificationOptionAudioPlayback": "အသံဖွင့်ခြင်း စတင်ပါပြီ။", - "NotificationOptionApplicationUpdateInstalled": "အပလီကေးရှင်း အပ်ဒိတ်ကို ထည့်သွင်းထားသည်။", - "NotificationOptionApplicationUpdateAvailable": "အပလီကေးရှင်း အပ်ဒိတ် ရနိုင်ပါပြီ။", + "NotificationOptionServerRestartRequired": "ဆာဗာ ပြန်လည်စတင်ရန် လိုအပ်သည်", + "NotificationOptionPluginUpdateInstalled": "ပလပ်အင် အပ်ဒိတ် ထည့်သွင်းပြီးပါပြီ", + "NotificationOptionPluginUninstalled": "ပလပ်အင်ကို ဖြုတ်လိုက်ပါပြီ", + "NotificationOptionPluginInstalled": "ပလပ်အင် ထည့်သွင်းထားသည်", + "NotificationOptionPluginError": "ပလပ်အင် ချို့ယွင်းခြင်း", + "NotificationOptionNewLibraryContent": "အသစ်များ ထပ်ထည့်ထားပါတယ်", + "NotificationOptionInstallationFailed": "ထည့်သွင်းမှု မအောင်မြင်ပါ", + "NotificationOptionCameraImageUploaded": "ကင်မရာမှ ဓာတ်ပုံ အပ်လုဒ် ပြီးပါပြီ", + "NotificationOptionAudioPlaybackStopped": "အသံဖိုင်ဖွင့်ခြင်း ရပ်သွားသည်", + "NotificationOptionAudioPlayback": "အသံဖွင့်ခြင်း စတင်ပါပြီ", + "NotificationOptionApplicationUpdateInstalled": "အပလီကေးရှင်း အပ်ဒိတ်ကို ထည့်သွင်းထားသည်", + "NotificationOptionApplicationUpdateAvailable": "အပလီကေးရှင်း အပ်ဒိတ် ရနိုင်ပါပြီ", "NewVersionIsAvailable": "Jellyfin Server ၏ ဗားရှင်းအသစ်ကို ဒေါင်းလုဒ်လုပ်နိုင်ပါပြီ။", "NameSeasonUnknown": "ဇာတ်လမ်းတွဲ အပိုင်းမသိ", "NameSeasonNumber": "ဇာတ်လမ်းတွဲ အပိုင်း {0}", - "NameInstallFailed": "{0} ထည့်သွင်းမှု မအောင်မြင်ပါ။", + "NameInstallFailed": "{0} ထည့်သွင်းမှု မအောင်မြင်ပါ", "MusicVideos": "ဂီတဗီဒီယိုများ", "Music": "တေးဂီတ", "Movies": "ရုပ်ရှင်များ", "MixedContent": "ရောနှောပါဝင်မှု", - "MessageServerConfigurationUpdated": "ဆာဗာဖွဲ့စည်းပုံကို အပ်ဒိတ်လုပ်ပြီးပါပြီ။", - "MessageNamedServerConfigurationUpdatedWithValue": "ဆာဗာဖွဲ့စည်းပုံကဏ္ဍ {0} ကို အပ်ဒိတ်လုပ်ပြီးပါပြီ။", + "MessageServerConfigurationUpdated": "ဆာဗာဖွဲ့စည်းပုံကို အပ်ဒိတ်လုပ်ပြီးပါပြီ", + "MessageNamedServerConfigurationUpdatedWithValue": "ဆာဗာဖွဲ့စည်းပုံကဏ္ဍ {0} ကို အပ်ဒိတ်လုပ်ပြီးပါပြီ", "MessageApplicationUpdatedTo": "Jellyfin ဆာဗာကို {0} သို့ အပ်ဒိတ်လုပ်ထားသည်", - "MessageApplicationUpdated": "Jellyfin ဆာဗာကို အပ်ဒိတ်လုပ်ပြီးပါပြီ။", + "MessageApplicationUpdated": "Jellyfin ဆာဗာကို အပ်ဒိတ်လုပ်ပြီးပါပြီ", "Latest": "နောက်ဆုံး", "LabelRunningTimeValue": "ကြာချိန် - {0}", "LabelIpAddressValue": "IP လိပ်စာ- {0}", - "ItemRemovedWithName": "{0} ကို ဒစ်ဂျစ်တိုက်မှ ဖယ်ရှားခဲ့သည်။", - "ItemAddedWithName": "{0} ကို စာကြည့်တိုက်သို့ ထည့်ထားသည်။", - "Inherit": "ဆက်ခံ၍ လုပ်ဆောင်သည်။", + "ItemRemovedWithName": "{0} ကို ဒစ်ဂျစ်တိုက်မှ ဖယ်ရှားခဲ့သည်", + "ItemAddedWithName": "{0} ကို စာကြည့်တိုက်သို့ ထည့်ထားသည်", + "Inherit": "ဆက်ခံ၍ လုပ်ဆောင်သည်", "HomeVideos": "ကိုယ်တိုင်ရိုက် ဗီဒီယိုများ", "HeaderRecordingGroups": "အသံဖမ်းအဖွဲ့များ", "HeaderNextUp": "နောက်ထပ်", @@ -106,18 +106,18 @@ "HeaderFavoriteEpisodes": "အကြိုက်ဆုံး ဇာတ်လမ်းအပိုင်းများ", "HeaderFavoriteArtists": "အကြိုက်ဆုံးအနုပညာရှင်များ", "HeaderFavoriteAlbums": "အကြိုက်ဆုံး အယ်လ်ဘမ်များ", - "HeaderContinueWatching": "ဆက်လက်ကြည့်ရှုပါ။", + "HeaderContinueWatching": "ဆက်လက်ကြည့်ရှုပါ", "HeaderAlbumArtists": "အယ်လ်ဘမ်အနုပညာရှင်များ", "Genres": "အမျိုးအစားများ", "Forced": "အတင်းအကြပ်", "Folders": "ဖိုလ်ဒါများ", "Favorites": "အကြိုက်ဆုံးများ", "FailedLoginAttemptWithUserName": "{0} မှ အကောင့်ဝင်ရန် မအောင်မြင်ပါ", - "DeviceOnlineWithName": "{0} ကို ချိတ်ဆက်ထားသည်။", - "DeviceOfflineWithName": "{0} နှင့် အဆက်ပြတ်သွားပါပြီ။", + "DeviceOnlineWithName": "{0} ကို ချိတ်ဆက်ထားသည်", + "DeviceOfflineWithName": "{0} နှင့် အဆက်ပြတ်သွားပါပြီ", "ChapterNameValue": "အခန်း {0}", - "CameraImageUploadedFrom": "ကင်မရာပုံအသစ်ကို {0} မှ ထည့်သွင်းလိုက်သည်။", - "AuthenticationSucceededWithUserName": "{0} စစ်မှန်ကြောင်း အောင်မြင်စွာ အတည်ပြုပြီးပါပြီ။", + "CameraImageUploadedFrom": "ကင်မရာပုံအသစ်ကို {0} မှ ထည့်သွင်းလိုက်သည်", + "AuthenticationSucceededWithUserName": "{0} အောင်မြင်စွာ စစ်မှန်ကြောင်း အတည်ပြုပြီးပါပြီ", "Application": "အပလီကေးရှင်း", "AppDeviceValues": "အက်ပ်- {0}၊ စက်- {1}", "External": "ပြင်ပ" diff --git a/Emby.Server.Implementations/Localization/Core/sl-SI.json b/Emby.Server.Implementations/Localization/Core/sl-SI.json index 30b24e9f0d..b2d7ce11df 100644 --- a/Emby.Server.Implementations/Localization/Core/sl-SI.json +++ b/Emby.Server.Implementations/Localization/Core/sl-SI.json @@ -122,5 +122,6 @@ "TaskOptimizeDatabaseDescription": "Stisne bazo podatkov in uredi prazen prostor. Zagon tega opravila po iskanju predstavnosti ali drugih spremembah ki vplivajo na bazo podatkov lahko izboljša hitrost delovanja.", "TaskOptimizeDatabase": "Optimiziraj bazo podatkov", "TaskKeyframeExtractor": "Ekstraktor ključnih sličic", - "External": "Zunanje" + "External": "Zunanji", + "TaskKeyframeExtractorDescription": "Iz video datoteke Izvleče ključne sličice, da ustvari bolj natančne sezname predvajanja HLS. Proces lahko traja dolgo časa." } diff --git a/Emby.Server.Implementations/Localization/Core/sr.json b/Emby.Server.Implementations/Localization/Core/sr.json index 781e93926e..1be8867f47 100644 --- a/Emby.Server.Implementations/Localization/Core/sr.json +++ b/Emby.Server.Implementations/Localization/Core/sr.json @@ -86,7 +86,7 @@ "Channels": "Канали", "CameraImageUploadedFrom": "Нова фотографија је учитана са {0}", "Books": "Књиге", - "AuthenticationSucceededWithUserName": "{0} Успешна аутентикација", + "AuthenticationSucceededWithUserName": "{0} Успешна аутентификација", "Artists": "Извођачи", "Application": "Апликација", "AppDeviceValues": "Апликација: {0}, Уређај: {1}", @@ -118,7 +118,7 @@ "Undefined": "Недефинисано", "Forced": "Принудно", "Default": "Подразумевано", - "TaskOptimizeDatabase": "Оптимизуј датабазу", + "TaskOptimizeDatabase": "Оптимизуј банку података", "TaskOptimizeDatabaseDescription": "Сажима базу података и скраћује слободан простор. Покретање овог задатка након скенирања библиотеке или других промена које подразумевају измене базе података које могу побољшати перформансе.", "External": "Спољно", "TaskKeyframeExtractorDescription": "Екстрактује кљулне сличице из видео датотека да би креирао више преицзну HLS плеј-листу. Овај задатак може да потраје дуже време.", diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index d25376297f..7f927e2709 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1242,7 +1242,7 @@ namespace Emby.Server.Implementations.Session if (item == null) { - _logger.LogError("A non-existant item Id {0} was passed into TranslateItemForPlayback", id); + _logger.LogError("A non-existent item Id {0} was passed into TranslateItemForPlayback", id); return Array.Empty<BaseItem>(); } diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index a085ee546d..fccf50f60f 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.Session private const float ForceKeepAliveFactor = 0.75f; /// <summary> - /// Lock used for accesing the KeepAlive cancellation token. + /// Lock used for accessing the KeepAlive cancellation token. /// </summary> private readonly object _keepAliveLock = new object(); diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs index 54ac06276e..94f7a7b827 100644 --- a/Jellyfin.Api/Controllers/AudioController.cs +++ b/Jellyfin.Api/Controllers/AudioController.cs @@ -207,7 +207,7 @@ namespace Jellyfin.Api.Controllers /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param> /// <param name="playSessionId">The play session id.</param> /// <param name="segmentContainer">The segment container.</param> - /// <param name="segmentLength">The segment lenght.</param> + /// <param name="segmentLength">The segment length.</param> /// <param name="minSegments">The minimum number of segments.</param> /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 1e8d038751..3ed80f6621 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -121,7 +121,7 @@ namespace Jellyfin.Api.Controllers /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param> /// <param name="playSessionId">The play session id.</param> /// <param name="segmentContainer">The segment container.</param> - /// <param name="segmentLength">The segment lenght.</param> + /// <param name="segmentLength">The segment length.</param> /// <param name="minSegments">The minimum number of segments.</param> /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> @@ -1832,7 +1832,7 @@ namespace Jellyfin.Api.Controllers // Set the key frame params for video encoding to match the hls segment time. args += _encodingHelper.GetHlsVideoKeyFrameArguments(state, codec, state.SegmentLength, isEventPlaylist, startNumber); - // Currenly b-frames in libx265 breaks the FMP4-HLS playback on iOS, disable it for now. + // Currently b-frames in libx265 breaks the FMP4-HLS playback on iOS, disable it for now. if (string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase)) { args += " -bf 0"; diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 1d207d9ad3..4d09070dbb 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; @@ -9,6 +10,7 @@ using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -32,6 +34,7 @@ namespace Jellyfin.Api.Controllers private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _localization; private readonly IDtoService _dtoService; + private readonly IAuthorizationContext _authContext; private readonly ILogger<ItemsController> _logger; private readonly ISessionManager _sessionManager; @@ -42,6 +45,7 @@ namespace Jellyfin.Api.Controllers /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> + /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param> /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param> public ItemsController( @@ -49,6 +53,7 @@ namespace Jellyfin.Api.Controllers ILibraryManager libraryManager, ILocalizationManager localization, IDtoService dtoService, + IAuthorizationContext authContext, ILogger<ItemsController> logger, ISessionManager sessionManager) { @@ -56,6 +61,7 @@ namespace Jellyfin.Api.Controllers _libraryManager = libraryManager; _localization = localization; _dtoService = dtoService; + _authContext = authContext; _logger = logger; _sessionManager = sessionManager; } @@ -63,7 +69,7 @@ namespace Jellyfin.Api.Controllers /// <summary> /// Gets items based on a query. /// </summary> - /// <param name="userId">The user id supplied as query parameter.</param> + /// <param name="userId">The user id supplied as query parameter; this is required when not using an API key.</param> /// <param name="maxOfficialRating">Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).</param> /// <param name="hasThemeSong">Optional filter by items with theme songs.</param> /// <param name="hasThemeVideo">Optional filter by items with theme videos.</param> @@ -151,8 +157,8 @@ namespace Jellyfin.Api.Controllers /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns> [HttpGet("Items")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<QueryResult<BaseItemDto>> GetItems( - [FromQuery] Guid userId, + public async Task<ActionResult<QueryResult<BaseItemDto>>> GetItems( + [FromQuery] Guid? userId, [FromQuery] string? maxOfficialRating, [FromQuery] bool? hasThemeSong, [FromQuery] bool? hasThemeVideo, @@ -238,7 +244,19 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool enableTotalRecordCount = true, [FromQuery] bool? enableImages = true) { - var user = userId.Equals(default) ? null : _userManager.GetUserById(userId); + var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); + + // if api key is used (auth.IsApiKey == true), then `user` will be null throughout this method + var user = !auth.IsApiKey && userId.HasValue && !userId.Value.Equals(default) + ? _userManager.GetUserById(userId.Value) + : null; + + // beyond this point, we're either using an api key or we have a valid user + if (!auth.IsApiKey && user is null) + { + return BadRequest("userId is required"); + } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); @@ -270,30 +288,39 @@ namespace Jellyfin.Api.Controllers includeItemTypes = new[] { BaseItemKind.Playlist }; } - var enabledChannels = user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels); + var enabledChannels = auth.IsApiKey + ? Array.Empty<Guid>() + : user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels); - bool isInEnabledFolder = Array.IndexOf(user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders), item.Id) != -1 + // api keys are always enabled for all folders + bool isInEnabledFolder = auth.IsApiKey + || Array.IndexOf(user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders), item.Id) != -1 // Assume all folders inside an EnabledChannel are enabled || Array.IndexOf(enabledChannels, item.Id) != -1 // Assume all items inside an EnabledChannel are enabled || Array.IndexOf(enabledChannels, item.ChannelId) != -1; - var collectionFolders = _libraryManager.GetCollectionFolders(item); - foreach (var collectionFolder in collectionFolders) + if (!isInEnabledFolder) { - if (user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders).Contains(collectionFolder.Id)) + var collectionFolders = _libraryManager.GetCollectionFolders(item); + foreach (var collectionFolder in collectionFolders) { - isInEnabledFolder = true; + // api keys never enter this block, so user is never null + if (user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders).Contains(collectionFolder.Id)) + { + isInEnabledFolder = true; + } } } + // api keys are always enabled for all folders, so user is never null if (item is not UserRootFolder && !isInEnabledFolder - && !user.HasPermission(PermissionKind.EnableAllFolders) + && !user!.HasPermission(PermissionKind.EnableAllFolders) && !user.HasPermission(PermissionKind.EnableAllChannels) && !string.Equals(collectionType, CollectionType.Folders, StringComparison.OrdinalIgnoreCase)) { - _logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Username, item.Name); + _logger.LogWarning("{UserName} is not permitted to access Library {ItemName}", user.Username, item.Name); return Unauthorized($"{user.Username} is not permitted to access Library {item.Name}."); } @@ -606,7 +633,7 @@ namespace Jellyfin.Api.Controllers /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns> [HttpGet("Users/{userId}/Items")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<QueryResult<BaseItemDto>> GetItemsByUserId( + public Task<ActionResult<QueryResult<BaseItemDto>>> GetItemsByUserId( [FromRoute] Guid userId, [FromQuery] string? maxOfficialRating, [FromQuery] bool? hasThemeSong, diff --git a/Jellyfin.Api/Controllers/QuickConnectController.cs b/Jellyfin.Api/Controllers/QuickConnectController.cs index 87b78fe93f..1df26355f0 100644 --- a/Jellyfin.Api/Controllers/QuickConnectController.cs +++ b/Jellyfin.Api/Controllers/QuickConnectController.cs @@ -39,7 +39,7 @@ namespace Jellyfin.Api.Controllers /// <returns>Whether Quick Connect is enabled on the server or not.</returns> [HttpGet("Enabled")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<bool> GetEnabled() + public ActionResult<bool> GetQuickConnectEnabled() { return _quickConnect.IsEnabled; } @@ -52,7 +52,7 @@ namespace Jellyfin.Api.Controllers /// <returns>A <see cref="QuickConnectResult"/> with a secret and code for future use or an error message.</returns> [HttpGet("Initiate")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task<ActionResult<QuickConnectResult>> Initiate() + public async Task<ActionResult<QuickConnectResult>> InitiateQuickConnect() { try { @@ -75,7 +75,7 @@ namespace Jellyfin.Api.Controllers [HttpGet("Connect")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult<QuickConnectResult> Connect([FromQuery, Required] string secret) + public ActionResult<QuickConnectResult> GetQuickConnectState([FromQuery, Required] string secret) { try { @@ -102,7 +102,7 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task<ActionResult<bool>> Authorize([FromQuery, Required] string code) + public async Task<ActionResult<bool>> AuthorizeQuickConnect([FromQuery, Required] string code) { var userId = ClaimHelpers.GetUserId(Request.HttpContext.User); if (!userId.HasValue) diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs index 07e113ad3e..aeed0c0d61 100644 --- a/Jellyfin.Api/Controllers/SearchController.cs +++ b/Jellyfin.Api/Controllers/SearchController.cs @@ -60,9 +60,9 @@ namespace Jellyfin.Api.Controllers /// <param name="limit">Optional. The maximum number of records to return.</param> /// <param name="userId">Optional. Supply a user id to search within a user's library or omit to search all.</param> /// <param name="searchTerm">The search term to filter on.</param> - /// <param name="includeItemTypes">If specified, only results with the specified item types are returned. This allows multiple, comma delimeted.</param> - /// <param name="excludeItemTypes">If specified, results with these item types are filtered out. This allows multiple, comma delimeted.</param> - /// <param name="mediaTypes">If specified, only results with the specified media types are returned. This allows multiple, comma delimeted.</param> + /// <param name="includeItemTypes">If specified, only results with the specified item types are returned. This allows multiple, comma delimited.</param> + /// <param name="excludeItemTypes">If specified, results with these item types are filtered out. This allows multiple, comma delimited.</param> + /// <param name="mediaTypes">If specified, only results with the specified media types are returned. This allows multiple, comma delimited.</param> /// <param name="parentId">If specified, only children of the parent are returned.</param> /// <param name="isMovie">Optional filter for movies.</param> /// <param name="isSeries">Optional filter for series.</param> @@ -79,7 +79,7 @@ namespace Jellyfin.Api.Controllers [HttpGet] [Description("Gets search hints based on a search term")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<SearchHintResult> Get( + public ActionResult<SearchHintResult> GetSearchHints( [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] Guid? userId, @@ -140,7 +140,7 @@ namespace Jellyfin.Api.Controllers IndexNumber = item.IndexNumber, ParentIndexNumber = item.ParentIndexNumber, Id = item.Id, - Type = item.GetClientTypeName(), + Type = item.GetBaseItemKind(), MediaType = item.MediaType, MatchedTerm = hintInfo.MatchedTerm, RunTimeTicks = item.RunTimeTicks, @@ -149,8 +149,10 @@ namespace Jellyfin.Api.Controllers EndDate = item.EndDate }; - // legacy +#pragma warning disable CS0618 + // Kept for compatibility with older clients result.ItemId = result.Id; +#pragma warning restore CS0618 if (item.IsFolder) { diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs index 1c5aa9b8e3..cf812fa237 100644 --- a/Jellyfin.Api/Controllers/TrailersController.cs +++ b/Jellyfin.Api/Controllers/TrailersController.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; @@ -31,7 +32,7 @@ namespace Jellyfin.Api.Controllers /// <summary> /// Finds movies and trailers similar to a given trailer. /// </summary> - /// <param name="userId">The user id.</param> + /// <param name="userId">The user id supplied as query parameter; this is required when not using an API key.</param> /// <param name="maxOfficialRating">Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).</param> /// <param name="hasThemeSong">Optional filter by items with theme songs.</param> /// <param name="hasThemeVideo">Optional filter by items with theme videos.</param> @@ -118,8 +119,8 @@ namespace Jellyfin.Api.Controllers /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the trailers.</returns> [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<QueryResult<BaseItemDto>> GetTrailers( - [FromQuery] Guid userId, + public Task<ActionResult<QueryResult<BaseItemDto>>> GetTrailers( + [FromQuery] Guid? userId, [FromQuery] string? maxOfficialRating, [FromQuery] bool? hasThemeSong, [FromQuery] bool? hasThemeVideo, diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index 82c8563a80..d1109bebc8 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -502,7 +502,7 @@ namespace Jellyfin.Api.Controllers if (isLocal) { - _logger.LogWarning("Password reset proccess initiated from outside the local network with IP: {IP}", ip); + _logger.LogWarning("Password reset process initiated from outside the local network with IP: {IP}", ip); } var result = await _userManager.StartForgotPasswordProcess(forgotPasswordRequest.EnteredUsername, isLocal).ConfigureAwait(false); diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs b/Jellyfin.Api/Models/StreamingDtos/StreamState.cs index 192f33ebd1..8182e3c9e8 100644 --- a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs +++ b/Jellyfin.Api/Models/StreamingDtos/StreamState.cs @@ -169,7 +169,7 @@ namespace Jellyfin.Api.Models.StreamingDtos /// <summary> /// Disposes the stream state. /// </summary> - /// <param name="disposing">Whether the object is currently beeing disposed.</param> + /// <param name="disposing">Whether the object is currently being disposed.</param> protected virtual void Dispose(bool disposing) { if (_disposed) diff --git a/Jellyfin.Api/Models/SyncPlayDtos/RemoveFromPlaylistRequestDto.cs b/Jellyfin.Api/Models/SyncPlayDtos/RemoveFromPlaylistRequestDto.cs index 02ce5a0488..226a584e1d 100644 --- a/Jellyfin.Api/Models/SyncPlayDtos/RemoveFromPlaylistRequestDto.cs +++ b/Jellyfin.Api/Models/SyncPlayDtos/RemoveFromPlaylistRequestDto.cs @@ -17,9 +17,9 @@ namespace Jellyfin.Api.Models.SyncPlayDtos } /// <summary> - /// Gets or sets the playlist identifiers ot the items. Ignored when clearing the playlist. + /// Gets or sets the playlist identifiers of the items. Ignored when clearing the playlist. /// </summary> - /// <value>The playlist identifiers ot the items.</value> + /// <value>The playlist identifiers of the items.</value> public IReadOnlyList<Guid> PlaylistItemIds { get; set; } /// <summary> diff --git a/Jellyfin.Drawing.Skia/SkiaHelper.cs b/Jellyfin.Drawing.Skia/SkiaHelper.cs index c001c32b8c..0478fc7c31 100644 --- a/Jellyfin.Drawing.Skia/SkiaHelper.cs +++ b/Jellyfin.Drawing.Skia/SkiaHelper.cs @@ -13,7 +13,7 @@ namespace Jellyfin.Drawing.Skia /// </summary> /// <param name="skiaEncoder">The current skia encoder.</param> /// <param name="paths">The list of image paths.</param> - /// <param name="currentIndex">The current checked indes.</param> + /// <param name="currentIndex">The current checked index.</param> /// <param name="newIndex">The new index.</param> /// <returns>A valid bitmap, or null if no bitmap exists after <c>currentIndex</c>.</returns> public static SKBitmap? GetNextValidImage(SkiaEncoder skiaEncoder, IReadOnlyList<string> paths, int currentIndex, out int newIndex) diff --git a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs index 61db223d92..361dbc8142 100644 --- a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs +++ b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs @@ -193,7 +193,7 @@ namespace Jellyfin.Networking.Configuration public bool AutoDiscovery { get; set; } = true; /// <summary> - /// Gets or sets the filter for remote IP connectivity. Used in conjuntion with <seealso cref="IsRemoteIPFilterBlacklist"/>. + /// Gets or sets the filter for remote IP connectivity. Used in conjunction with <seealso cref="IsRemoteIPFilterBlacklist"/>. /// </summary> public string[] RemoteIPFilter { get; set; } = Array.Empty<string>(); diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 4b7b87814c..fd0665dbdd 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -944,7 +944,7 @@ namespace Jellyfin.Networking.Manager // Add virtual machine interface names to the list of bind exclusions, so that they are auto-excluded. if (config.IgnoreVirtualInterfaces) { - // each virtual interface name must be pre-pended with the exclusion symbol ! + // each virtual interface name must be prepended with the exclusion symbol ! var virtualInterfaceNames = config.VirtualInterfaceNames.Split(',').Select(p => "!" + p).ToArray(); if (lanAddresses.Length > 0) { diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index 8eb5f21960..1954a5c558 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -103,6 +104,22 @@ namespace Jellyfin.Server }) .ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate); + services.AddHttpClient(NamedClient.Dlna, c => + { + c.DefaultRequestHeaders.UserAgent.ParseAdd( + string.Format( + CultureInfo.InvariantCulture, + "{0}/{1} UPnP/1.0 {2}/{3}", + MediaBrowser.Common.System.OperatingSystem.Name, + Environment.OSVersion, + _serverApplicationHost.Name, + _serverApplicationHost.ApplicationVersionString)); + + c.DefaultRequestHeaders.Add("CPFN.UPNP.ORG", _serverApplicationHost.FriendlyName); // Required for UPnP DeviceArchitecture v2.0 + c.DefaultRequestHeaders.Add("FriendlyName.DLNA.ORG", _serverApplicationHost.FriendlyName); // REVIEW: where does this come from? + }) + .ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate); + services.AddHealthChecks() .AddDbContextCheck<JellyfinDb>(); diff --git a/MediaBrowser.Common/Net/NamedClient.cs b/MediaBrowser.Common/Net/NamedClient.cs index 0f6161c328..a6cacd4f17 100644 --- a/MediaBrowser.Common/Net/NamedClient.cs +++ b/MediaBrowser.Common/Net/NamedClient.cs @@ -14,5 +14,10 @@ /// Gets the value for the MusicBrainz named http client. /// </summary> public const string MusicBrainz = nameof(MusicBrainz); + + /// <summary> + /// Gets the value for the DLNA named http client. + /// </summary> + public const string Dlna = nameof(Dlna); } } diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index bd8df2facb..599d35da64 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -244,7 +244,7 @@ namespace MediaBrowser.Controller.Entities.TV /// <summary> /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// </summary> - /// <param name="replaceAllMetadata"><c>true</c> to replace metdata, <c>false</c> to not.</param> + /// <param name="replaceAllMetadata"><c>true</c> to replace metadata, <c>false</c> to not.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index e5aae620ac..17e410fe13 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -194,7 +194,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// <summary> /// Gets the name of the output video codec. /// </summary> - /// <param name="state">Encording state.</param> + /// <param name="state">Encoding state.</param> /// <param name="encodingOptions">Encoding options.</param> /// <returns>Encoder string.</returns> public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions) @@ -1980,7 +1980,7 @@ namespace MediaBrowser.Controller.MediaEncoding } } - // Cap the max target bitrate to intMax/2 to satisify the bufsize=bitrate*2. + // Cap the max target bitrate to intMax/2 to satisfy the bufsize=bitrate*2. return Math.Min(bitrate ?? 0, int.MaxValue / 2); } diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs index a0c38b3097..2164945560 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs @@ -549,7 +549,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates if (InitialState.Equals(GroupStateType.Playing)) { - // Group went from playing to waiting state and a pause request occured while waiting. + // Group went from playing to waiting state and a pause request occurred while waiting. var pauseRequest = new PauseGroupRequest(); pausedState.HandleRequest(pauseRequest, context, Type, session, cancellationToken); } diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs index 2f38d6adc3..619294e957 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs @@ -27,9 +27,9 @@ namespace MediaBrowser.Controller.SyncPlay.PlaybackRequests } /// <summary> - /// Gets the playlist identifiers ot the items. + /// Gets the playlist identifiers of the items. /// </summary> - /// <value>The playlist identifiers ot the items.</value> + /// <value>The playlist identifiers of the items.</value> public IReadOnlyList<Guid> PlaylistItemIds { get; } /// <summary> diff --git a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs index f49876cca4..3a7685f347 100644 --- a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs +++ b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs @@ -102,7 +102,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue } /// <summary> - /// Appends new items to the playlist. The specified order is mantained. + /// Appends new items to the playlist. The specified order is maintained. /// </summary> /// <param name="items">The items to add to the playlist.</param> public void Queue(IReadOnlyList<Guid> items) @@ -197,7 +197,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue } /// <summary> - /// Adds new items to the playlist right after the playing item. The specified order is mantained. + /// Adds new items to the playlist right after the playing item. The specified order is maintained. /// </summary> /// <param name="items">The items to add to the playlist.</param> public void QueueNext(IReadOnlyList<Guid> items) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 77b97c9b48..7f301a9d8b 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -619,9 +619,9 @@ namespace MediaBrowser.MediaEncoding.Encoder Video3DFormat.HalfSideBySide => "crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1", // fsbs crop width in half,set the display aspect,crop out any black bars we may have made Video3DFormat.FullSideBySide => "crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1", - // htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made + // htab crop height in half,scale to correct size, set the display aspect,crop out any black bars we may have made Video3DFormat.HalfTopAndBottom => "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1", - // ftab crop heigt in half, set the display aspect,crop out any black bars we may have made + // ftab crop height in half, set the display aspect,crop out any black bars we may have made Video3DFormat.FullTopAndBottom => "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1", _ => "scale=trunc(iw*sar):ih" }; diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 6112fda680..8e1acc46c9 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -574,7 +574,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles throw; } - var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(5)).ConfigureAwait(false); + var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false); if (!ranToCompletion) { diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs index 47c36494bd..c32c1c108c 100644 --- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs +++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs @@ -157,7 +157,7 @@ namespace MediaBrowser.Model.Dlna flagValue |= DlnaFlags.ByteBasedSeek; } - // Time based seek is curently disabled when streaming. On LG CX3 adding DlnaFlags.TimeBasedSeek and orgPn causes the DLNA playback to fail (format not supported). Further investigations are needed before enabling the remaining code paths. + // Time based seek is currently disabled when streaming. On LG CX3 adding DlnaFlags.TimeBasedSeek and orgPn causes the DLNA playback to fail (format not supported). Further investigations are needed before enabling the remaining code paths. // else if (runtimeTicks.HasValue) // { // flagValue = flagValue | DlnaFlags.TimeBasedSeek; diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 094dc73b27..fdb84fa320 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -294,13 +294,13 @@ namespace MediaBrowser.Model.Dto public NameGuidPair[] GenreItems { get; set; } /// <summary> - /// Gets or sets wether the item has a logo, this will hold the Id of the Parent that has one. + /// Gets or sets whether the item has a logo, this will hold the Id of the Parent that has one. /// </summary> /// <value>The parent logo item id.</value> public Guid? ParentLogoItemId { get; set; } /// <summary> - /// Gets or sets wether the item has any backdrops, this will hold the Id of the Parent that has one. + /// Gets or sets whether the item has any backdrops, this will hold the Id of the Parent that has one. /// </summary> /// <value>The parent backdrop item id.</value> public Guid? ParentBackdropItemId { get; set; } @@ -506,7 +506,7 @@ namespace MediaBrowser.Model.Dto public string ParentLogoImageTag { get; set; } /// <summary> - /// Gets or sets wether the item has fan art, this will hold the Id of the Parent that has one. + /// Gets or sets whether the item has fan art, this will hold the Id of the Parent that has one. /// </summary> /// <value>The parent art item id.</value> public Guid? ParentArtItemId { get; set; } diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index ae8f3b0edf..90a60cf470 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -606,7 +606,7 @@ namespace MediaBrowser.Model.Entities <= 1024 when Height <= 576 => IsInterlaced ? "576i" : "576p", // 1280x720 <= 1280 when Height <= 962 => IsInterlaced ? "720i" : "720p", - // 2560x1080 (FHD ultra wide 21:9) using 1440px width to accomodate WQHD + // 2560x1080 (FHD ultra wide 21:9) using 1440px width to accommodate WQHD <= 2560 when Height <= 1440 => IsInterlaced ? "1080i" : "1080p", // 4K <= 4096 when Height <= 3072 => "4K", diff --git a/MediaBrowser.Model/LiveTv/TunerHostInfo.cs b/MediaBrowser.Model/LiveTv/TunerHostInfo.cs index 05576a0f8d..a832169c2a 100644 --- a/MediaBrowser.Model/LiveTv/TunerHostInfo.cs +++ b/MediaBrowser.Model/LiveTv/TunerHostInfo.cs @@ -8,6 +8,7 @@ namespace MediaBrowser.Model.LiveTv public TunerHostInfo() { AllowHWTranscoding = true; + IgnoreDts = true; } public string Id { get; set; } @@ -31,5 +32,7 @@ namespace MediaBrowser.Model.LiveTv public int TunerCount { get; set; } public string UserAgent { get; set; } + + public bool IgnoreDts { get; set; } } } diff --git a/MediaBrowser.Model/Search/SearchHint.cs b/MediaBrowser.Model/Search/SearchHint.cs index 983dbd2bc0..4696c3797b 100644 --- a/MediaBrowser.Model/Search/SearchHint.cs +++ b/MediaBrowser.Model/Search/SearchHint.cs @@ -1,8 +1,6 @@ -#nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; +using Jellyfin.Data.Enums; namespace MediaBrowser.Model.Search { @@ -12,11 +10,27 @@ namespace MediaBrowser.Model.Search public class SearchHint { /// <summary> + /// Initializes a new instance of the <see cref="SearchHint" /> class. + /// </summary> + public SearchHint() + { + Name = string.Empty; + MatchedTerm = string.Empty; + MediaType = string.Empty; + Artists = Array.Empty<string>(); + } + + /// <summary> /// Gets or sets the item id. /// </summary> /// <value>The item id.</value> + [Obsolete("Use Id instead")] public Guid ItemId { get; set; } + /// <summary> + /// Gets or sets the item id. + /// </summary> + /// <value>The item id.</value> public Guid Id { get; set; } /// <summary> @@ -53,38 +67,42 @@ namespace MediaBrowser.Model.Search /// Gets or sets the image tag. /// </summary> /// <value>The image tag.</value> - public string PrimaryImageTag { get; set; } + public string? PrimaryImageTag { get; set; } /// <summary> /// Gets or sets the thumb image tag. /// </summary> /// <value>The thumb image tag.</value> - public string ThumbImageTag { get; set; } + public string? ThumbImageTag { get; set; } /// <summary> /// Gets or sets the thumb image item identifier. /// </summary> /// <value>The thumb image item identifier.</value> - public string ThumbImageItemId { get; set; } + public string? ThumbImageItemId { get; set; } /// <summary> /// Gets or sets the backdrop image tag. /// </summary> /// <value>The backdrop image tag.</value> - public string BackdropImageTag { get; set; } + public string? BackdropImageTag { get; set; } /// <summary> /// Gets or sets the backdrop image item identifier. /// </summary> /// <value>The backdrop image item identifier.</value> - public string BackdropImageItemId { get; set; } + public string? BackdropImageItemId { get; set; } /// <summary> /// Gets or sets the type. /// </summary> /// <value>The type.</value> - public string Type { get; set; } + public BaseItemKind Type { get; set; } + /// <summary> + /// Gets a value indicating whether this instance is folder. + /// </summary> + /// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value> public bool? IsFolder { get; set; } /// <summary> @@ -99,31 +117,47 @@ namespace MediaBrowser.Model.Search /// <value>The type of the media.</value> public string MediaType { get; set; } + /// <summary> + /// Gets or sets the start date. + /// </summary> + /// <value>The start date.</value> public DateTime? StartDate { get; set; } + /// <summary> + /// Gets or sets the end date. + /// </summary> + /// <value>The end date.</value> public DateTime? EndDate { get; set; } /// <summary> /// Gets or sets the series. /// </summary> /// <value>The series.</value> - public string Series { get; set; } + public string? Series { get; set; } - public string Status { get; set; } + /// <summary> + /// Gets or sets the status. + /// </summary> + /// <value>The status.</value> + public string? Status { get; set; } /// <summary> /// Gets or sets the album. /// </summary> /// <value>The album.</value> - public string Album { get; set; } + public string? Album { get; set; } - public Guid AlbumId { get; set; } + /// <summary> + /// Gets or sets the album id. + /// </summary> + /// <value>The album id.</value> + public Guid? AlbumId { get; set; } /// <summary> /// Gets or sets the album artist. /// </summary> /// <value>The album artist.</value> - public string AlbumArtist { get; set; } + public string? AlbumArtist { get; set; } /// <summary> /// Gets or sets the artists. @@ -147,13 +181,13 @@ namespace MediaBrowser.Model.Search /// Gets or sets the channel identifier. /// </summary> /// <value>The channel identifier.</value> - public Guid ChannelId { get; set; } + public Guid? ChannelId { get; set; } /// <summary> /// Gets or sets the name of the channel. /// </summary> /// <value>The name of the channel.</value> - public string ChannelName { get; set; } + public string? ChannelName { get; set; } /// <summary> /// Gets or sets the primary image aspect ratio. diff --git a/MediaBrowser.Model/SyncPlay/GroupStateType.cs b/MediaBrowser.Model/SyncPlay/GroupStateType.cs index 7aa454f928..96364cacc7 100644 --- a/MediaBrowser.Model/SyncPlay/GroupStateType.cs +++ b/MediaBrowser.Model/SyncPlay/GroupStateType.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Model.SyncPlay Idle = 0, /// <summary> - /// The group is in wating state. Playback is paused. Will start playing when users are ready. + /// The group is in waiting state. Playback is paused. Will start playing when users are ready. /// </summary> Waiting = 1, diff --git a/MediaBrowser.Model/Tasks/ITaskManager.cs b/MediaBrowser.Model/Tasks/ITaskManager.cs index a86bf2a1c8..13bebc479e 100644 --- a/MediaBrowser.Model/Tasks/ITaskManager.cs +++ b/MediaBrowser.Model/Tasks/ITaskManager.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Model.Tasks /// <summary> /// Cancels if running and queue. /// </summary> - /// <typeparam name="T">An implementatin of <see cref="IScheduledTask" />.</typeparam> + /// <typeparam name="T">An implementation of <see cref="IScheduledTask" />.</typeparam> /// <param name="options">Task options.</param> void CancelIfRunningAndQueue<T>(TaskOptions options) where T : IScheduledTask; @@ -30,21 +30,21 @@ namespace MediaBrowser.Model.Tasks /// <summary> /// Cancels if running and queue. /// </summary> - /// <typeparam name="T">An implementatin of <see cref="IScheduledTask" />.</typeparam> + /// <typeparam name="T">An implementation of <see cref="IScheduledTask" />.</typeparam> void CancelIfRunningAndQueue<T>() where T : IScheduledTask; /// <summary> /// Cancels if running. /// </summary> - /// <typeparam name="T">An implementatin of <see cref="IScheduledTask" />.</typeparam> + /// <typeparam name="T">An implementation of <see cref="IScheduledTask" />.</typeparam> void CancelIfRunning<T>() where T : IScheduledTask; /// <summary> /// Queues the scheduled task. /// </summary> - /// <typeparam name="T">An implementatin of <see cref="IScheduledTask" />.</typeparam> + /// <typeparam name="T">An implementation of <see cref="IScheduledTask" />.</typeparam> /// <param name="options">Task options.</param> void QueueScheduledTask<T>(TaskOptions options) where T : IScheduledTask; @@ -52,7 +52,7 @@ namespace MediaBrowser.Model.Tasks /// <summary> /// Queues the scheduled task. /// </summary> - /// <typeparam name="T">An implementatin of <see cref="IScheduledTask" />.</typeparam> + /// <typeparam name="T">An implementation of <see cref="IScheduledTask" />.</typeparam> void QueueScheduledTask<T>() where T : IScheduledTask; diff --git a/MediaBrowser.Model/Tasks/ITaskTrigger.cs b/MediaBrowser.Model/Tasks/ITaskTrigger.cs index 8c3ec6626c..0536f4ef77 100644 --- a/MediaBrowser.Model/Tasks/ITaskTrigger.cs +++ b/MediaBrowser.Model/Tasks/ITaskTrigger.cs @@ -21,10 +21,10 @@ namespace MediaBrowser.Model.Tasks /// <summary> /// Stars waiting for the trigger action. /// </summary> - /// <param name="lastResult">Result of the last run triggerd task.</param> + /// <param name="lastResult">Result of the last run triggered task.</param> /// <param name="logger">The <see cref="ILogger"/>.</param> /// <param name="taskName">The name of the task.</param> - /// <param name="isApplicationStartup">Wheter or not this is is fired during startup.</param> + /// <param name="isApplicationStartup">Whether or not this is is fired during startup.</param> void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup); /// <summary> diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 01ff473f0c..bbb33ddf0e 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -926,7 +926,7 @@ namespace MediaBrowser.Providers.Manager } catch (Exception ex) { - _logger.LogError(ex, "Error in {0}.Suports", i.GetType().Name); + _logger.LogError(ex, "Error in {0}.Supports", i.GetType().Name); return false; } }); diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs index 4bf66c0988..915fb97fd2 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Music /// <summary> /// The Jellyfin user-agent is unrestricted but source IP must not exceed /// one request per second, therefore we rate limit to avoid throttling. - /// Be prudent, use a value slightly above the minimun required. + /// Be prudent, use a value slightly above the minimum required. /// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting. /// </summary> private readonly long _musicBrainzQueryIntervalMs; diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 09ff84044d..da348239a1 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -1330,7 +1330,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers }; /// <summary> - /// Used to split names of comma or pipe delimeted genres and people. + /// Used to split names of comma or pipe delimited genres and people. /// </summary> /// <param name="value">The value.</param> /// <returns>IEnumerable{System.String}.</returns> diff --git a/deployment/Dockerfile.linux.musl-linux-arm64 b/deployment/Dockerfile.linux.musl-linux-arm64 new file mode 100644 index 0000000000..2da72e4ae0 --- /dev/null +++ b/deployment/Dockerfile.linux.musl-linux-arm64 @@ -0,0 +1,26 @@ +FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG ARTIFACT_DIR=/dist +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist +ENV DEB_BUILD_OPTIONS=noddebs +ENV ARCH=arm64 +ENV IS_DOCKER=YES + +# Prepare Debian build environment +RUN apt-get update -yqq \ + && apt-get install -yqq --no-install-recommends \ + apt-transport-https debhelper gnupg devscripts unzip \ + mmv libcurl4-openssl-dev libfontconfig1-dev \ + libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 + +# Link to docker-build script +RUN ln -sf ${SOURCE_DIR}/deployment/build.linux.musl-linux-arm64 /build.sh + +VOLUME ${SOURCE_DIR}/ + +VOLUME ${ARTIFACT_DIR}/ + +ENTRYPOINT ["/build.sh"] diff --git a/deployment/build.linux.musl-linux-arm64 b/deployment/build.linux.musl-linux-arm64 new file mode 100644 index 0000000000..e1ccbc266e --- /dev/null +++ b/deployment/build.linux.musl-linux-arm64 @@ -0,0 +1,31 @@ +#!/bin/bash + +#= Generic Linux musl-linux-arm64 .tar.gz + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +# Get version +if [[ ${IS_UNSTABLE} == 'yes' ]]; then + version="${BUILD_ID}" +else + version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" +fi + +# Build archives +dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-musl-arm64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true" +tar -czf jellyfin-server_${version}_linux-arm64-musl.tar.gz -C dist jellyfin-server_${version} +rm -rf dist/jellyfin-server_${version} + +# Move the artifacts out +mkdir -p ${ARTIFACT_DIR}/ +mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/ + +if [[ ${IS_DOCKER} == YES ]]; then + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} +fi + +popd diff --git a/fedora/jellyfin.env b/fedora/jellyfin.env index 89cad1a2d3..1ccd8196fd 100644 --- a/fedora/jellyfin.env +++ b/fedora/jellyfin.env @@ -33,7 +33,7 @@ JELLYFIN_RESTART_OPT="--restartpath=/usr/libexec/jellyfin/restart.sh" #JELLYFIN_SERVICE_OPT="--service" # [OPTIONAL] run Jellyfin without the web app -#JELLYFIN_NOWEBAPP_OPT="--noautorunwebapp" +#JELLYFIN_NOWEBAPP_OPT="--nowebclient" # [OPTIONAL] run Jellyfin with ASP.NET Server Garbage Collection (uses more RAM and less CPU than Workstation GC) # 0 = Workstation diff --git a/src/Jellyfin.Extensions/SplitStringExtensions.cs b/src/Jellyfin.Extensions/SplitStringExtensions.cs index 1d1c377f56..a4dc9fc6b1 100644 --- a/src/Jellyfin.Extensions/SplitStringExtensions.cs +++ b/src/Jellyfin.Extensions/SplitStringExtensions.cs @@ -55,7 +55,7 @@ namespace Jellyfin.Extensions public static Enumerator Split(this ReadOnlySpan<char> str, char separator) => new(str, separator); /// <summary> - /// Provides an enumerator for the substrings seperated by the separator. + /// Provides an enumerator for the substrings separated by the separator. /// </summary> [StructLayout(LayoutKind.Auto)] public ref struct Enumerator diff --git a/src/Jellyfin.Extensions/StringExtensions.cs b/src/Jellyfin.Extensions/StringExtensions.cs index dadc9f1d5c..59fb038a75 100644 --- a/src/Jellyfin.Extensions/StringExtensions.cs +++ b/src/Jellyfin.Extensions/StringExtensions.cs @@ -40,7 +40,7 @@ namespace Jellyfin.Extensions } /// <summary> - /// Checks wether or not the specified string has diacritics in it. + /// Checks whether or not the specified string has diacritics in it. /// </summary> /// <param name="text">The string to check.</param> /// <returns>True if the string has diacritics, false otherwise.</returns> diff --git a/tests/Jellyfin.Model.Tests/Cryptography/PasswordHashTests.cs b/tests/Jellyfin.Model.Tests/Cryptography/PasswordHashTests.cs index 6948280a3d..162f53e567 100644 --- a/tests/Jellyfin.Model.Tests/Cryptography/PasswordHashTests.cs +++ b/tests/Jellyfin.Model.Tests/Cryptography/PasswordHashTests.cs @@ -152,9 +152,9 @@ namespace Jellyfin.Model.Tests.Cryptography [InlineData("$PBKDF2$$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Empty segment [InlineData("$PBKDF2$iterations=1000$$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Empty salt segment [InlineData("$PBKDF2$iterations=1000$69F420$")] // Empty hash segment - [InlineData("$PBKDF2$=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter - [InlineData("$PBKDF2$=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter - [InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter + [InlineData("$PBKDF2$=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parameter + [InlineData("$PBKDF2$=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parameter + [InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parameter [InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Ends on $ [InlineData("$PBKDF2$iterations=1000$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Extra segment [InlineData("$PBKDF2$iterations=1000$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$anotherone")] // Extra segment diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs index 6b93974373..52b0e5a955 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -393,7 +393,7 @@ namespace Jellyfin.Networking.Tests // User on external network, internal binding only - so assumption is a proxy forward, return external override. [InlineData("jellyfin.org", "192.168.1.0/24", "eth16", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")] - // User on external network, no binding - so result is the 1st external which is overriden. + // User on external network, no binding - so result is the 1st external which is overridden. [InlineData("jellyfin.org", "192.168.1.0/24", "", false, "0.0.0.0 = http://helloworld.com", "http://helloworld.com")] // User assumed to be internal, no binding - so result is the 1st internal. diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs index 3b3e38bd1f..e1d2bb2d58 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs @@ -18,7 +18,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect } /// <summary> - /// /token reponse. + /// /token response. /// </summary> [Fact] public void Deserialize_Token_Response_Live_Success() diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/manifest-stable.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/manifest-stable.json index b766e668e3..fa8fbd8d2c 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/manifest-stable.json +++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/manifest-stable.json @@ -253,7 +253,7 @@ "versions": [ { "version": "5.0.0.0", - "changelog": "Updated to use NextPVR API v5, no longer compatable with API v4.\n", + "changelog": "Updated to use NextPVR API v5, no longer compatible with API v4.\n", "targetAbi": "10.7.0.0", "sourceUrl": "https://repo.jellyfin.org/releases/plugin/nextpvr/nextpvr_5.0.0.0.zip", "checksum": "d70f694d14bf9462ba2b2ebe110068d3", |
