mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-11-04 03:27:21 -05:00 
			
		
		
		
	Merge remote-tracking branch 'upstream/master' into SubnetOverlappFix
This commit is contained in:
		
						commit
						4c7680e186
					
				@ -94,5 +94,5 @@ jobs:
 | 
			
		||||
        displayName: 'Publish OpenAPI Artifact'
 | 
			
		||||
        condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
 | 
			
		||||
        inputs:
 | 
			
		||||
          targetPath: "tests/Jellyfin.Api.Tests/bin/Release/net5.0/openapi.json"
 | 
			
		||||
          targetPath: "tests/Jellyfin.Server.Integration.Tests/bin/Release/net5.0/openapi.json"
 | 
			
		||||
          artifactName: 'OpenAPI Spec'
 | 
			
		||||
 | 
			
		||||
@ -49,6 +49,7 @@
 | 
			
		||||
 - [h1nk](https://github.com/h1nk)
 | 
			
		||||
 - [hawken93](https://github.com/hawken93)
 | 
			
		||||
 - [HelloWorld017](https://github.com/HelloWorld017)
 | 
			
		||||
 - [ikomhoog](https://github.com/ikomhoog)
 | 
			
		||||
 - [jftuga](https://github.com/jftuga)
 | 
			
		||||
 - [joern-h](https://github.com/joern-h)
 | 
			
		||||
 - [joshuaboniface](https://github.com/joshuaboniface)
 | 
			
		||||
 | 
			
		||||
@ -36,7 +36,7 @@ namespace Emby.Dlna
 | 
			
		||||
        private readonly ILogger<DlnaManager> _logger;
 | 
			
		||||
        private readonly IServerApplicationHost _appHost;
 | 
			
		||||
        private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
 | 
			
		||||
        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
 | 
			
		||||
        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
 | 
			
		||||
 | 
			
		||||
        private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal);
 | 
			
		||||
 | 
			
		||||
@ -395,7 +395,8 @@ namespace Emby.Dlna
 | 
			
		||||
                    {
 | 
			
		||||
                        Directory.CreateDirectory(systemProfilesPath);
 | 
			
		||||
 | 
			
		||||
                        using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
 | 
			
		||||
                        // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
 | 
			
		||||
                        using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
 | 
			
		||||
                        {
 | 
			
		||||
                            await stream.CopyToAsync(fileStream).ConfigureAwait(false);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,6 @@
 | 
			
		||||
 | 
			
		||||
  <!-- Code Analyzers-->
 | 
			
		||||
  <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
 | 
			
		||||
    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
 | 
			
		||||
 | 
			
		||||
@ -316,7 +316,7 @@ namespace Emby.Dlna.Main
 | 
			
		||||
                _logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);
 | 
			
		||||
 | 
			
		||||
                var uri = new UriBuilder(_appHost.GetSmartApiUrl(address.Address) + descriptorUri);
 | 
			
		||||
                if (_appHost.PublishedServerUrl == null)
 | 
			
		||||
                if (!string.IsNullOrEmpty(_appHost.PublishedServerUrl))
 | 
			
		||||
                {
 | 
			
		||||
                    // DLNA will only work over http, so we must reset to http:// : {port}.
 | 
			
		||||
                    uri.Scheme = "http";
 | 
			
		||||
 | 
			
		||||
@ -132,7 +132,7 @@ namespace Emby.Dlna.PlayTo
 | 
			
		||||
 | 
			
		||||
        private async void OnDeviceMediaChanged(object sender, MediaChangedEventArgs e)
 | 
			
		||||
        {
 | 
			
		||||
            if (_disposed)
 | 
			
		||||
            if (_disposed || string.IsNullOrEmpty(e.OldMediaInfo.Url))
 | 
			
		||||
            {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -45,10 +45,10 @@ namespace Emby.Dlna.PlayTo
 | 
			
		||||
                    cancellationToken)
 | 
			
		||||
                .ConfigureAwait(false);
 | 
			
		||||
            await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
 | 
			
		||||
            using var reader = new StreamReader(stream, Encoding.UTF8);
 | 
			
		||||
            return XDocument.Parse(
 | 
			
		||||
                await reader.ReadToEndAsync().ConfigureAwait(false),
 | 
			
		||||
                LoadOptions.PreserveWhitespace);
 | 
			
		||||
            return await XDocument.LoadAsync(
 | 
			
		||||
                stream,
 | 
			
		||||
                LoadOptions.PreserveWhitespace,
 | 
			
		||||
                cancellationToken).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
 | 
			
		||||
@ -94,10 +94,10 @@ namespace Emby.Dlna.PlayTo
 | 
			
		||||
            options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
 | 
			
		||||
            using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
            await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
 | 
			
		||||
            using var reader = new StreamReader(stream, Encoding.UTF8);
 | 
			
		||||
            return XDocument.Parse(
 | 
			
		||||
                await reader.ReadToEndAsync().ConfigureAwait(false),
 | 
			
		||||
                LoadOptions.PreserveWhitespace);
 | 
			
		||||
            return await XDocument.LoadAsync(
 | 
			
		||||
                stream,
 | 
			
		||||
                LoadOptions.PreserveWhitespace,
 | 
			
		||||
                cancellationToken).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task<HttpResponseMessage> PostSoapDataAsync(
 | 
			
		||||
 | 
			
		||||
@ -13,12 +13,10 @@ namespace Emby.Dlna.PlayTo
 | 
			
		||||
    public class TransportCommands
 | 
			
		||||
    {
 | 
			
		||||
        private const string CommandBase = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<SOAP-ENV:Body>" + "<m:{0} xmlns:m=\"{1}\">" + "{2}" + "</m:{0}>" + "</SOAP-ENV:Body></SOAP-ENV:Envelope>";
 | 
			
		||||
        private List<StateVariable> _stateVariables = new List<StateVariable>();
 | 
			
		||||
        private List<ServiceAction> _serviceActions = new List<ServiceAction>();
 | 
			
		||||
 | 
			
		||||
        public List<StateVariable> StateVariables => _stateVariables;
 | 
			
		||||
        public List<StateVariable> StateVariables { get; } = new List<StateVariable>();
 | 
			
		||||
 | 
			
		||||
        public List<ServiceAction> ServiceActions => _serviceActions;
 | 
			
		||||
        public List<ServiceAction> ServiceActions { get; } = new List<ServiceAction>();
 | 
			
		||||
 | 
			
		||||
        public static TransportCommands Create(XDocument document)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,7 @@
 | 
			
		||||
#pragma warning disable CS1591
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using MediaBrowser.Model.Dlna;
 | 
			
		||||
 | 
			
		||||
@ -10,6 +12,7 @@ namespace Emby.Dlna.Profiles
 | 
			
		||||
    {
 | 
			
		||||
        public DefaultProfile()
 | 
			
		||||
        {
 | 
			
		||||
            Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
 | 
			
		||||
            Name = "Generic Device";
 | 
			
		||||
 | 
			
		||||
            ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*";
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,6 @@
 | 
			
		||||
 | 
			
		||||
  <!-- Code analysers-->
 | 
			
		||||
  <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
 | 
			
		||||
    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
 | 
			
		||||
 | 
			
		||||
@ -44,7 +44,6 @@
 | 
			
		||||
 | 
			
		||||
  <!-- Code Analyzers-->
 | 
			
		||||
  <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
 | 
			
		||||
    <!-- TODO: <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> -->
 | 
			
		||||
    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Diagnostics.CodeAnalysis;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
 | 
			
		||||
namespace Emby.Naming.Video
 | 
			
		||||
@ -16,8 +17,14 @@ namespace Emby.Naming.Video
 | 
			
		||||
        /// <param name="expressions">List of regex to parse name and year from.</param>
 | 
			
		||||
        /// <param name="newName">Parsing result string.</param>
 | 
			
		||||
        /// <returns>True if parsing was successful.</returns>
 | 
			
		||||
        public static bool TryClean(string name, IReadOnlyList<Regex> expressions, out ReadOnlySpan<char> newName)
 | 
			
		||||
        public static bool TryClean([NotNullWhen(true)] string? name, IReadOnlyList<Regex> expressions, out ReadOnlySpan<char> newName)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrEmpty(name))
 | 
			
		||||
            {
 | 
			
		||||
                newName = ReadOnlySpan<char>.Empty;
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var len = expressions.Count;
 | 
			
		||||
            for (int i = 0; i < len; i++)
 | 
			
		||||
            {
 | 
			
		||||
@ -41,7 +48,7 @@ namespace Emby.Naming.Video
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            newName = string.Empty;
 | 
			
		||||
            newName = ReadOnlySpan<char>.Empty;
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -221,20 +221,22 @@ namespace Emby.Naming.Video
 | 
			
		||||
            string testFilename = Path.GetFileNameWithoutExtension(testFilePath);
 | 
			
		||||
            if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
 | 
			
		||||
            {
 | 
			
		||||
                if (CleanStringParser.TryClean(testFilename, _options.CleanStringRegexes, out var cleanName))
 | 
			
		||||
                {
 | 
			
		||||
                    testFilename = cleanName.ToString();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Remove the folder name before cleaning as we don't care about cleaning that part
 | 
			
		||||
                if (folderName.Length <= testFilename.Length)
 | 
			
		||||
                {
 | 
			
		||||
                    testFilename = testFilename.Substring(folderName.Length).Trim();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (CleanStringParser.TryClean(testFilename, _options.CleanStringRegexes, out var cleanName))
 | 
			
		||||
                {
 | 
			
		||||
                    testFilename = cleanName.Trim().ToString();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // The CleanStringParser should have removed common keywords etc.
 | 
			
		||||
                return string.IsNullOrEmpty(testFilename)
 | 
			
		||||
                       || testFilename[0] == '-'
 | 
			
		||||
                       || testFilename[0] == '_'
 | 
			
		||||
                   || string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty));
 | 
			
		||||
                       || Regex.IsMatch(testFilename, @"^\[([^]]*)\]");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Diagnostics.CodeAnalysis;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Emby.Naming.Common;
 | 
			
		||||
@ -146,7 +147,7 @@ namespace Emby.Naming.Video
 | 
			
		||||
        /// <param name="name">Raw name.</param>
 | 
			
		||||
        /// <param name="newName">Clean name.</param>
 | 
			
		||||
        /// <returns>True if cleaning of name was successful.</returns>
 | 
			
		||||
        public bool TryCleanString(string name, out ReadOnlySpan<char> newName)
 | 
			
		||||
        public bool TryCleanString([NotNullWhen(true)] string? name, out ReadOnlySpan<char> newName)
 | 
			
		||||
        {
 | 
			
		||||
            return CleanStringParser.TryClean(name, _options.CleanStringRegexes, out newName);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,6 @@
 | 
			
		||||
 | 
			
		||||
  <!-- Code analyzers-->
 | 
			
		||||
  <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
 | 
			
		||||
    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,6 @@
 | 
			
		||||
 | 
			
		||||
  <!-- Code Analyzers-->
 | 
			
		||||
  <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
 | 
			
		||||
    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
 | 
			
		||||
 | 
			
		||||
@ -53,7 +53,8 @@ namespace Emby.Server.Implementations.AppBase
 | 
			
		||||
 | 
			
		||||
                Directory.CreateDirectory(directory);
 | 
			
		||||
                // Save it after load in case we got new items
 | 
			
		||||
                using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
 | 
			
		||||
                // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
 | 
			
		||||
                using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
 | 
			
		||||
                {
 | 
			
		||||
                    fs.Write(newBytes, 0, newBytesLen);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -10,8 +10,6 @@ using System.Net;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
using System.Security.Cryptography.X509Certificates;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Text.Json;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Emby.Dlna;
 | 
			
		||||
@ -43,6 +41,7 @@ using Emby.Server.Implementations.Serialization;
 | 
			
		||||
using Emby.Server.Implementations.Session;
 | 
			
		||||
using Emby.Server.Implementations.SyncPlay;
 | 
			
		||||
using Emby.Server.Implementations.TV;
 | 
			
		||||
using Emby.Server.Implementations.Udp;
 | 
			
		||||
using Emby.Server.Implementations.Updates;
 | 
			
		||||
using Jellyfin.Api.Helpers;
 | 
			
		||||
using Jellyfin.Networking.Configuration;
 | 
			
		||||
@ -50,7 +49,6 @@ using Jellyfin.Networking.Manager;
 | 
			
		||||
using MediaBrowser.Common;
 | 
			
		||||
using MediaBrowser.Common.Configuration;
 | 
			
		||||
using MediaBrowser.Common.Events;
 | 
			
		||||
using MediaBrowser.Common.Json;
 | 
			
		||||
using MediaBrowser.Common.Net;
 | 
			
		||||
using MediaBrowser.Common.Plugins;
 | 
			
		||||
using MediaBrowser.Common.Updates;
 | 
			
		||||
@ -99,6 +97,7 @@ using MediaBrowser.Providers.Subtitles;
 | 
			
		||||
using MediaBrowser.XbmcMetadata.Providers;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.Extensions.Configuration;
 | 
			
		||||
using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
using Microsoft.Extensions.Logging;
 | 
			
		||||
using Prometheus.DotNetRuntime;
 | 
			
		||||
@ -118,6 +117,7 @@ namespace Emby.Server.Implementations
 | 
			
		||||
        private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" };
 | 
			
		||||
 | 
			
		||||
        private readonly IFileSystem _fileSystemManager;
 | 
			
		||||
        private readonly IConfiguration _startupConfig;
 | 
			
		||||
        private readonly IXmlSerializer _xmlSerializer;
 | 
			
		||||
        private readonly IStartupOptions _startupOptions;
 | 
			
		||||
        private readonly IPluginManager _pluginManager;
 | 
			
		||||
@ -134,9 +134,6 @@ namespace Emby.Server.Implementations
 | 
			
		||||
 | 
			
		||||
        public bool CoreStartupHasCompleted { get; private set; }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        public Uri PublishedServerUrl => _startupOptions.PublishedServerUrl;
 | 
			
		||||
 | 
			
		||||
        public virtual bool CanLaunchWebBrowser
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
@ -230,6 +227,11 @@ namespace Emby.Server.Implementations
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int HttpsPort { get; private set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the value of the PublishedServerUrl setting.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string PublishedServerUrl => _startupOptions.PublishedServerUrl ?? _startupConfig[UdpServer.AddressOverrideConfigKey];
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the server configuration manager.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
@ -242,12 +244,14 @@ namespace Emby.Server.Implementations
 | 
			
		||||
        /// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param>
 | 
			
		||||
        /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
 | 
			
		||||
        /// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param>
 | 
			
		||||
        /// <param name="startupConfig">The <see cref="IConfiguration" /> interface.</param>
 | 
			
		||||
        /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
 | 
			
		||||
        /// <param name="serviceCollection">Instance of the <see cref="IServiceCollection"/> interface.</param>
 | 
			
		||||
        public ApplicationHost(
 | 
			
		||||
            IServerApplicationPaths applicationPaths,
 | 
			
		||||
            ILoggerFactory loggerFactory,
 | 
			
		||||
            IStartupOptions options,
 | 
			
		||||
            IConfiguration startupConfig,
 | 
			
		||||
            IFileSystem fileSystem,
 | 
			
		||||
            IServiceCollection serviceCollection)
 | 
			
		||||
        {
 | 
			
		||||
@ -270,6 +274,7 @@ namespace Emby.Server.Implementations
 | 
			
		||||
            Logger = LoggerFactory.CreateLogger<ApplicationHost>();
 | 
			
		||||
 | 
			
		||||
            _startupOptions = options;
 | 
			
		||||
            _startupConfig = startupConfig;
 | 
			
		||||
 | 
			
		||||
            // Initialize runtime stat collection
 | 
			
		||||
            if (ServerConfigurationManager.Configuration.EnableMetrics)
 | 
			
		||||
@ -462,7 +467,7 @@ namespace Emby.Server.Implementations
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        public IReadOnlyCollection<T> GetExports<T>(CreationDelegate defaultFunc, bool manageLifetime = true)
 | 
			
		||||
        public IReadOnlyCollection<T> GetExports<T>(CreationDelegateFactory defaultFunc, bool manageLifetime = true)
 | 
			
		||||
        {
 | 
			
		||||
            // Convert to list so this isn't executed for each iteration
 | 
			
		||||
            var parts = GetExportTypes<T>()
 | 
			
		||||
@ -1155,10 +1160,10 @@ namespace Emby.Server.Implementations
 | 
			
		||||
        public string GetSmartApiUrl(IPAddress ipAddress, int? port = null)
 | 
			
		||||
        {
 | 
			
		||||
            // Published server ends with a /
 | 
			
		||||
            if (_startupOptions.PublishedServerUrl != null)
 | 
			
		||||
            if (!string.IsNullOrEmpty(PublishedServerUrl))
 | 
			
		||||
            {
 | 
			
		||||
                // Published server ends with a '/', so we need to remove it.
 | 
			
		||||
                return _startupOptions.PublishedServerUrl.ToString().Trim('/');
 | 
			
		||||
                return PublishedServerUrl.Trim('/');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            string smart = NetManager.GetBindInterface(ipAddress, out port);
 | 
			
		||||
@ -1175,10 +1180,10 @@ namespace Emby.Server.Implementations
 | 
			
		||||
        public string GetSmartApiUrl(HttpRequest request, int? port = null)
 | 
			
		||||
        {
 | 
			
		||||
            // Published server ends with a /
 | 
			
		||||
            if (_startupOptions.PublishedServerUrl != null)
 | 
			
		||||
            if (!string.IsNullOrEmpty(PublishedServerUrl))
 | 
			
		||||
            {
 | 
			
		||||
                // Published server ends with a '/', so we need to remove it.
 | 
			
		||||
                return _startupOptions.PublishedServerUrl.ToString().Trim('/');
 | 
			
		||||
                return PublishedServerUrl.Trim('/');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            string smart = NetManager.GetBindInterface(request, out port);
 | 
			
		||||
@ -1195,10 +1200,10 @@ namespace Emby.Server.Implementations
 | 
			
		||||
        public string GetSmartApiUrl(string hostname, int? port = null)
 | 
			
		||||
        {
 | 
			
		||||
            // Published server ends with a /
 | 
			
		||||
            if (_startupOptions.PublishedServerUrl != null)
 | 
			
		||||
            if (!string.IsNullOrEmpty(PublishedServerUrl))
 | 
			
		||||
            {
 | 
			
		||||
                // Published server ends with a '/', so we need to remove it.
 | 
			
		||||
                return _startupOptions.PublishedServerUrl.ToString().Trim('/');
 | 
			
		||||
                return PublishedServerUrl.Trim('/');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            string smart = NetManager.GetBindInterface(hostname, out port);
 | 
			
		||||
 | 
			
		||||
@ -49,7 +49,7 @@ namespace Emby.Server.Implementations.Channels
 | 
			
		||||
        private readonly IProviderManager _providerManager;
 | 
			
		||||
        private readonly IMemoryCache _memoryCache;
 | 
			
		||||
        private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
 | 
			
		||||
        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
 | 
			
		||||
        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Initializes a new instance of the <see cref="ChannelManager"/> class.
 | 
			
		||||
 | 
			
		||||
@ -343,11 +343,24 @@ namespace Emby.Server.Implementations.Collections
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        var alreadyInResults = false;
 | 
			
		||||
                        foreach (var child in item.GetMediaSources(true))
 | 
			
		||||
                        {
 | 
			
		||||
                            if (Guid.TryParse(child.Id, out var id) && results.ContainsKey(id))
 | 
			
		||||
                            {
 | 
			
		||||
                                alreadyInResults = true;
 | 
			
		||||
                                break;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (!alreadyInResults)
 | 
			
		||||
                        {
 | 
			
		||||
                            results[item.Id] = item;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return results.Values;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -88,7 +88,7 @@ namespace Emby.Server.Implementations.Data
 | 
			
		||||
            _imageProcessor = imageProcessor;
 | 
			
		||||
 | 
			
		||||
            _typeMapper = new TypeMapper();
 | 
			
		||||
            _jsonOptions = JsonDefaults.GetOptions();
 | 
			
		||||
            _jsonOptions = JsonDefaults.Options;
 | 
			
		||||
 | 
			
		||||
            DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -49,7 +49,6 @@
 | 
			
		||||
 | 
			
		||||
  <!-- Code Analyzers-->
 | 
			
		||||
  <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
 | 
			
		||||
    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
 | 
			
		||||
 | 
			
		||||
@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.HttpServer
 | 
			
		||||
            RemoteEndPoint = remoteEndPoint;
 | 
			
		||||
            QueryString = query;
 | 
			
		||||
 | 
			
		||||
            _jsonOptions = JsonDefaults.GetOptions();
 | 
			
		||||
            _jsonOptions = JsonDefaults.Options;
 | 
			
		||||
            LastActivityDate = DateTime.Now;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
#pragma warning disable CS1591
 | 
			
		||||
 | 
			
		||||
#nullable enable
 | 
			
		||||
using System;
 | 
			
		||||
 | 
			
		||||
namespace Emby.Server.Implementations
 | 
			
		||||
@ -9,7 +9,7 @@ namespace Emby.Server.Implementations
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the value of the --ffmpeg command line option.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        string FFmpegPath { get; }
 | 
			
		||||
        string? FFmpegPath { get; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the value of the --service command line option.
 | 
			
		||||
@ -19,21 +19,21 @@ namespace Emby.Server.Implementations
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the value of the --package-name command line option.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        string PackageName { get; }
 | 
			
		||||
        string? PackageName { get; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the value of the --restartpath command line option.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        string RestartPath { get; }
 | 
			
		||||
        string? RestartPath { get; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the value of the --restartargs command line option.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        string RestartArgs { get; }
 | 
			
		||||
        string? RestartArgs { get; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the value of the --published-server-url command line option.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        Uri PublishedServerUrl { get; }
 | 
			
		||||
        string? PublishedServerUrl { get; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1247,7 +1247,7 @@ namespace Emby.Server.Implementations.Library
 | 
			
		||||
            {
 | 
			
		||||
                // TODO: @bond use a ReadOnlySpan<char> here when Enum.TryParse supports it
 | 
			
		||||
                // https://github.com/dotnet/runtime/issues/20008
 | 
			
		||||
                if (Enum.TryParse<CollectionTypeOptions>(Path.GetExtension(file), true, out var res))
 | 
			
		||||
                if (Enum.TryParse<CollectionTypeOptions>(Path.GetFileNameWithoutExtension(file), true, out var res))
 | 
			
		||||
                {
 | 
			
		||||
                    return res;
 | 
			
		||||
                }
 | 
			
		||||
@ -2776,6 +2776,7 @@ namespace Emby.Server.Implementations.Library
 | 
			
		||||
 | 
			
		||||
        public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem)
 | 
			
		||||
        {
 | 
			
		||||
            string newPath;
 | 
			
		||||
            if (ownerItem != null)
 | 
			
		||||
            {
 | 
			
		||||
                var libraryOptions = GetLibraryOptions(ownerItem);
 | 
			
		||||
@ -2783,15 +2784,9 @@ namespace Emby.Server.Implementations.Library
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var pathInfo in libraryOptions.PathInfos)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (string.IsNullOrWhiteSpace(pathInfo.Path) || string.IsNullOrWhiteSpace(pathInfo.NetworkPath))
 | 
			
		||||
                        if (path.TryReplaceSubPath(pathInfo.Path, pathInfo.NetworkPath, out newPath))
 | 
			
		||||
                        {
 | 
			
		||||
                            continue;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        var substitutionResult = SubstitutePathInternal(path, pathInfo.Path, pathInfo.NetworkPath);
 | 
			
		||||
                        if (substitutionResult.Item2)
 | 
			
		||||
                        {
 | 
			
		||||
                            return substitutionResult.Item1;
 | 
			
		||||
                            return newPath;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
@ -2800,24 +2795,16 @@ namespace Emby.Server.Implementations.Library
 | 
			
		||||
            var metadataPath = _configurationManager.Configuration.MetadataPath;
 | 
			
		||||
            var metadataNetworkPath = _configurationManager.Configuration.MetadataNetworkPath;
 | 
			
		||||
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(metadataPath) && !string.IsNullOrWhiteSpace(metadataNetworkPath))
 | 
			
		||||
            if (path.TryReplaceSubPath(metadataPath, metadataNetworkPath, out newPath))
 | 
			
		||||
            {
 | 
			
		||||
                var metadataSubstitutionResult = SubstitutePathInternal(path, metadataPath, metadataNetworkPath);
 | 
			
		||||
                if (metadataSubstitutionResult.Item2)
 | 
			
		||||
                {
 | 
			
		||||
                    return metadataSubstitutionResult.Item1;
 | 
			
		||||
                }
 | 
			
		||||
                return newPath;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            foreach (var map in _configurationManager.Configuration.PathSubstitutions)
 | 
			
		||||
            {
 | 
			
		||||
                if (!string.IsNullOrWhiteSpace(map.From))
 | 
			
		||||
                if (path.TryReplaceSubPath(map.From, map.To, out newPath))
 | 
			
		||||
                {
 | 
			
		||||
                    var substitutionResult = SubstitutePathInternal(path, map.From, map.To);
 | 
			
		||||
                    if (substitutionResult.Item2)
 | 
			
		||||
                    {
 | 
			
		||||
                        return substitutionResult.Item1;
 | 
			
		||||
                    }
 | 
			
		||||
                    return newPath;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -2826,47 +2813,12 @@ namespace Emby.Server.Implementations.Library
 | 
			
		||||
 | 
			
		||||
        public string SubstitutePath(string path, string from, string to)
 | 
			
		||||
        {
 | 
			
		||||
            return SubstitutePathInternal(path, from, to).Item1;
 | 
			
		||||
            if (path.TryReplaceSubPath(from, to, out var newPath))
 | 
			
		||||
            {
 | 
			
		||||
                return newPath;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        private Tuple<string, bool> SubstitutePathInternal(string path, string from, string to)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(path))
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentNullException(nameof(path));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(from))
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentNullException(nameof(from));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(to))
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentNullException(nameof(to));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            from = from.Trim();
 | 
			
		||||
            to = to.Trim();
 | 
			
		||||
 | 
			
		||||
            var newPath = path.Replace(from, to, StringComparison.OrdinalIgnoreCase);
 | 
			
		||||
            var changed = false;
 | 
			
		||||
 | 
			
		||||
            if (!string.Equals(newPath, path, StringComparison.Ordinal))
 | 
			
		||||
            {
 | 
			
		||||
                if (to.IndexOf('/', StringComparison.Ordinal) != -1)
 | 
			
		||||
                {
 | 
			
		||||
                    newPath = newPath.Replace('\\', '/');
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    newPath = newPath.Replace('/', '\\');
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                changed = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new Tuple<string, bool>(newPath, changed);
 | 
			
		||||
            return path;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void SetExtraTypeFromFilename(Video item)
 | 
			
		||||
@ -3001,7 +2953,7 @@ namespace Emby.Server.Implementations.Library
 | 
			
		||||
 | 
			
		||||
                if (collectionType != null)
 | 
			
		||||
                {
 | 
			
		||||
                    var path = Path.Combine(virtualFolderPath, collectionType.ToString() + ".collection");
 | 
			
		||||
                    var path = Path.Combine(virtualFolderPath, collectionType.ToString().ToLowerInvariant() + ".collection");
 | 
			
		||||
 | 
			
		||||
                    File.WriteAllBytes(path, Array.Empty<byte>());
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Library
 | 
			
		||||
        private readonly IMediaEncoder _mediaEncoder;
 | 
			
		||||
        private readonly ILogger _logger;
 | 
			
		||||
        private readonly IApplicationPaths _appPaths;
 | 
			
		||||
        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
 | 
			
		||||
        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
 | 
			
		||||
 | 
			
		||||
        public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IApplicationPaths appPaths)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.Library
 | 
			
		||||
 | 
			
		||||
        private readonly ConcurrentDictionary<string, ILiveStream> _openStreams = new ConcurrentDictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase);
 | 
			
		||||
        private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
 | 
			
		||||
        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
 | 
			
		||||
        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
 | 
			
		||||
 | 
			
		||||
        private IMediaSourceProvider[] _providers;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,8 @@
 | 
			
		||||
#nullable enable
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Diagnostics.CodeAnalysis;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
 | 
			
		||||
namespace Emby.Server.Implementations.Library
 | 
			
		||||
@ -47,5 +49,66 @@ namespace Emby.Server.Implementations.Library
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Replaces a sub path with another sub path and normalizes the final path.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="path">The original path.</param>
 | 
			
		||||
        /// <param name="subPath">The original sub path.</param>
 | 
			
		||||
        /// <param name="newSubPath">The new sub path.</param>
 | 
			
		||||
        /// <param name="newPath">The result of the sub path replacement</param>
 | 
			
		||||
        /// <returns>The path after replacing the sub path.</returns>
 | 
			
		||||
        /// <exception cref="ArgumentNullException"><paramref name="path" />, <paramref name="newSubPath" /> or <paramref name="newSubPath" /> is empty.</exception>
 | 
			
		||||
        public static bool TryReplaceSubPath(
 | 
			
		||||
            [NotNullWhen(true)] this string? path,
 | 
			
		||||
            [NotNullWhen(true)] string? subPath,
 | 
			
		||||
            [NotNullWhen(true)] string? newSubPath,
 | 
			
		||||
            [NotNullWhen(true)] out string? newPath)
 | 
			
		||||
        {
 | 
			
		||||
            newPath = null;
 | 
			
		||||
 | 
			
		||||
            if (string.IsNullOrEmpty(path)
 | 
			
		||||
                || string.IsNullOrEmpty(subPath)
 | 
			
		||||
                || string.IsNullOrEmpty(newSubPath)
 | 
			
		||||
                || subPath.Length > path.Length)
 | 
			
		||||
            {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            char oldDirectorySeparatorChar;
 | 
			
		||||
            char newDirectorySeparatorChar;
 | 
			
		||||
            // True normalization is still not possible https://github.com/dotnet/runtime/issues/2162
 | 
			
		||||
            // The reasoning behind this is that a forward slash likely means it's a Linux path and
 | 
			
		||||
            // so the whole path should be normalized to use / and vice versa for Windows (although Windows doesn't care much).
 | 
			
		||||
            if (newSubPath.Contains('/', StringComparison.Ordinal))
 | 
			
		||||
            {
 | 
			
		||||
                oldDirectorySeparatorChar = '\\';
 | 
			
		||||
                newDirectorySeparatorChar = '/';
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                oldDirectorySeparatorChar = '/';
 | 
			
		||||
                newDirectorySeparatorChar = '\\';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            path = path.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar);
 | 
			
		||||
            subPath = subPath.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar);
 | 
			
		||||
 | 
			
		||||
            // We have to ensure that the sub path ends with a directory separator otherwise we'll get weird results
 | 
			
		||||
            // when the sub path matches a similar but in-complete subpath
 | 
			
		||||
            var oldSubPathEndsWithSeparator = subPath[^1] == newDirectorySeparatorChar;
 | 
			
		||||
            if (!path.StartsWith(subPath, StringComparison.OrdinalIgnoreCase)
 | 
			
		||||
                || (!oldSubPathEndsWithSeparator && path[subPath.Length] != newDirectorySeparatorChar))
 | 
			
		||||
            {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var newSubPathTrimmed = newSubPath.AsSpan().TrimEnd(newDirectorySeparatorChar);
 | 
			
		||||
            // Ensure that the path with the old subpath removed starts with a leading dir separator
 | 
			
		||||
            int idx = oldSubPathEndsWithSeparator ? subPath.Length - 1 : subPath.Length;
 | 
			
		||||
            newPath = string.Concat(newSubPathTrimmed, path.AsSpan(idx));
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -45,7 +45,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 | 
			
		||||
        {
 | 
			
		||||
            Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
 | 
			
		||||
 | 
			
		||||
            using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read))
 | 
			
		||||
            // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
 | 
			
		||||
            using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None))
 | 
			
		||||
            {
 | 
			
		||||
                onStarted();
 | 
			
		||||
 | 
			
		||||
@ -70,7 +71,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 | 
			
		||||
 | 
			
		||||
            Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
 | 
			
		||||
 | 
			
		||||
            await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read);
 | 
			
		||||
            // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
 | 
			
		||||
            await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None);
 | 
			
		||||
 | 
			
		||||
            onStarted();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1856,7 +1856,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read))
 | 
			
		||||
            // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
 | 
			
		||||
            using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.None))
 | 
			
		||||
            {
 | 
			
		||||
                var settings = new XmlWriterSettings
 | 
			
		||||
                {
 | 
			
		||||
@ -1920,7 +1921,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read))
 | 
			
		||||
            // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
 | 
			
		||||
            using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.None))
 | 
			
		||||
            {
 | 
			
		||||
                var settings = new XmlWriterSettings
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 | 
			
		||||
        private readonly IServerApplicationPaths _appPaths;
 | 
			
		||||
        private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
 | 
			
		||||
        private readonly IServerConfigurationManager _serverConfigurationManager;
 | 
			
		||||
        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
 | 
			
		||||
        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
 | 
			
		||||
        private bool _hasExited;
 | 
			
		||||
        private Stream _logFileStream;
 | 
			
		||||
        private string _targetPath;
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 | 
			
		||||
    {
 | 
			
		||||
        private readonly string _dataPath;
 | 
			
		||||
        private readonly object _fileDataLock = new object();
 | 
			
		||||
        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
 | 
			
		||||
        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
 | 
			
		||||
        private T[] _items;
 | 
			
		||||
 | 
			
		||||
        public ItemDataProvider(
 | 
			
		||||
 | 
			
		||||
@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
 | 
			
		||||
        private readonly ICryptoProvider _cryptoProvider;
 | 
			
		||||
 | 
			
		||||
        private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>();
 | 
			
		||||
        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
 | 
			
		||||
        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
 | 
			
		||||
        private DateTime _lastErrorResponse;
 | 
			
		||||
 | 
			
		||||
        public SchedulesDirect(
 | 
			
		||||
 | 
			
		||||
@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 | 
			
		||||
            _networkManager = networkManager;
 | 
			
		||||
            _streamHelper = streamHelper;
 | 
			
		||||
 | 
			
		||||
            _jsonOptions = JsonDefaults.GetOptions();
 | 
			
		||||
            _jsonOptions = JsonDefaults.Options;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string Name => "HD Homerun";
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Buffers;
 | 
			
		||||
using System.Buffers.Binary;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
using System.Net;
 | 
			
		||||
@ -10,6 +11,7 @@ using System.Text;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using MediaBrowser.Common;
 | 
			
		||||
using MediaBrowser.Controller.LiveTv;
 | 
			
		||||
 | 
			
		||||
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 | 
			
		||||
@ -120,13 +122,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 | 
			
		||||
 | 
			
		||||
        private static async Task<bool> CheckTunerAvailability(NetworkStream stream, int tuner, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var lockkeyMsg = CreateGetMessage(tuner, "lockkey");
 | 
			
		||||
            await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                var msgLen = WriteGetMessage(buffer, tuner, "lockkey");
 | 
			
		||||
                await stream.WriteAsync(buffer.AsMemory(0, msgLen), cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                ParseReturnMessage(buffer, receivedBytes, out string returnVal);
 | 
			
		||||
 | 
			
		||||
@ -166,9 +168,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 | 
			
		||||
 | 
			
		||||
                    _activeTuner = i;
 | 
			
		||||
                    var lockKeyString = string.Format(CultureInfo.InvariantCulture, "{0:d}", lockKeyValue);
 | 
			
		||||
                    var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null);
 | 
			
		||||
                    await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                    int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                    var lockkeyMsgLen = WriteSetMessage(buffer, i, "lockkey", lockKeyString, null);
 | 
			
		||||
                    await stream.WriteAsync(buffer.AsMemory(0, lockkeyMsgLen), cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                    int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    // parse response to make sure it worked
 | 
			
		||||
                    if (!ParseReturnMessage(buffer, receivedBytes, out _))
 | 
			
		||||
@ -178,9 +180,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 | 
			
		||||
 | 
			
		||||
                    foreach (var command in commands.GetCommands())
 | 
			
		||||
                    {
 | 
			
		||||
                        var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue);
 | 
			
		||||
                        await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                        receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                        var channelMsgLen = WriteSetMessage(buffer, i, command.Item1, command.Item2, lockKeyValue);
 | 
			
		||||
                        await stream.WriteAsync(buffer.AsMemory(0, channelMsgLen), cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                        receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                        // parse response to make sure it worked
 | 
			
		||||
                        if (!ParseReturnMessage(buffer, receivedBytes, out _))
 | 
			
		||||
@ -191,10 +193,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var targetValue = string.Format(CultureInfo.InvariantCulture, "rtp://{0}:{1}", localIp, localPort);
 | 
			
		||||
                    var targetMsg = CreateSetMessage(i, "target", targetValue, lockKeyValue);
 | 
			
		||||
                    var targetMsgLen = WriteSetMessage(buffer, i, "target", targetValue, lockKeyValue);
 | 
			
		||||
 | 
			
		||||
                    await stream.WriteAsync(targetMsg, 0, targetMsg.Length, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                    receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                    await stream.WriteAsync(buffer.AsMemory(0, targetMsgLen), cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                    receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    // parse response to make sure it worked
 | 
			
		||||
                    if (!ParseReturnMessage(buffer, receivedBytes, out _))
 | 
			
		||||
@ -232,9 +234,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var command in commandList)
 | 
			
		||||
                {
 | 
			
		||||
                    var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey);
 | 
			
		||||
                    await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                    int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                    var channelMsgLen = WriteSetMessage(buffer, _activeTuner, command.Item1, command.Item2, _lockkey);
 | 
			
		||||
                    await stream.WriteAsync(buffer.AsMemory(0, channelMsgLen), cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                    int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    // parse response to make sure it worked
 | 
			
		||||
                    if (!ParseReturnMessage(buffer, receivedBytes, out _))
 | 
			
		||||
@ -265,17 +267,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 | 
			
		||||
        {
 | 
			
		||||
            var stream = client.GetStream();
 | 
			
		||||
 | 
			
		||||
            var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", lockKeyValue);
 | 
			
		||||
            await stream.WriteAsync(releaseTarget, 0, releaseTarget.Length).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            var buffer = ArrayPool<byte>.Shared.Rent(8192);
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
 | 
			
		||||
                var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", lockKeyValue);
 | 
			
		||||
                var releaseTargetLen = WriteSetMessage(buffer, _activeTuner, "target", "none", lockKeyValue);
 | 
			
		||||
                await stream.WriteAsync(buffer.AsMemory(0, releaseTargetLen)).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                await stream.ReadAsync(buffer).ConfigureAwait(false);
 | 
			
		||||
                var releaseKeyMsgLen = WriteSetMessage(buffer, _activeTuner, "lockkey", "none", lockKeyValue);
 | 
			
		||||
                _lockkey = null;
 | 
			
		||||
                await stream.WriteAsync(releaseKeyMsg, 0, releaseKeyMsg.Length).ConfigureAwait(false);
 | 
			
		||||
                await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
 | 
			
		||||
                await stream.WriteAsync(buffer.AsMemory(0, releaseKeyMsgLen)).ConfigureAwait(false);
 | 
			
		||||
                await stream.ReadAsync(buffer).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
@ -283,109 +285,76 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static byte[] CreateGetMessage(int tuner, string name)
 | 
			
		||||
        internal static int WriteGetMessage(Span<byte> buffer, int tuner, string name)
 | 
			
		||||
        {
 | 
			
		||||
            var byteName = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}\0", tuner, name));
 | 
			
		||||
            int messageLength = byteName.Length + 10; // 4 bytes for header + 4 bytes for crc + 2 bytes for tag name and length
 | 
			
		||||
 | 
			
		||||
            var message = new byte[messageLength];
 | 
			
		||||
 | 
			
		||||
            int offset = InsertHeaderAndName(byteName, messageLength, message);
 | 
			
		||||
 | 
			
		||||
            bool flipEndian = BitConverter.IsLittleEndian;
 | 
			
		||||
 | 
			
		||||
            // calculate crc and insert at the end of the message
 | 
			
		||||
            var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4));
 | 
			
		||||
            if (flipEndian)
 | 
			
		||||
            {
 | 
			
		||||
                Array.Reverse(crcBytes);
 | 
			
		||||
            var byteName = string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}", tuner, name);
 | 
			
		||||
            int offset = WriteHeaderAndPayload(buffer, byteName);
 | 
			
		||||
            return FinishPacket(buffer, offset);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            Buffer.BlockCopy(crcBytes, 0, message, offset, 4);
 | 
			
		||||
 | 
			
		||||
            return message;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static byte[] CreateSetMessage(int tuner, string name, string value, uint? lockkey)
 | 
			
		||||
        private static int WriteSetMessage(Span<byte> buffer, int tuner, string name, string value, uint? lockkey)
 | 
			
		||||
        {
 | 
			
		||||
            var byteName = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}\0", tuner, name));
 | 
			
		||||
            var byteValue = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}\0", value));
 | 
			
		||||
            var byteName = string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}", tuner, name);
 | 
			
		||||
            int offset = WriteHeaderAndPayload(buffer, byteName);
 | 
			
		||||
 | 
			
		||||
            buffer[offset++] = GetSetValue;
 | 
			
		||||
            offset += WriteNullTerminatedString(buffer.Slice(offset), value);
 | 
			
		||||
 | 
			
		||||
            int messageLength = byteName.Length + byteValue.Length + 12;
 | 
			
		||||
            if (lockkey.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                messageLength += 6;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var message = new byte[messageLength];
 | 
			
		||||
 | 
			
		||||
            int offset = InsertHeaderAndName(byteName, messageLength, message);
 | 
			
		||||
 | 
			
		||||
            bool flipEndian = BitConverter.IsLittleEndian;
 | 
			
		||||
 | 
			
		||||
            message[offset++] = GetSetValue;
 | 
			
		||||
            message[offset++] = Convert.ToByte(byteValue.Length);
 | 
			
		||||
            Buffer.BlockCopy(byteValue, 0, message, offset, byteValue.Length);
 | 
			
		||||
            offset += byteValue.Length;
 | 
			
		||||
            if (lockkey.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                message[offset++] = GetSetLockkey;
 | 
			
		||||
                message[offset++] = 4;
 | 
			
		||||
                var lockKeyBytes = BitConverter.GetBytes(lockkey.Value);
 | 
			
		||||
                if (flipEndian)
 | 
			
		||||
                {
 | 
			
		||||
                    Array.Reverse(lockKeyBytes);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Buffer.BlockCopy(lockKeyBytes, 0, message, offset, 4);
 | 
			
		||||
                buffer[offset++] = GetSetLockkey;
 | 
			
		||||
                buffer[offset++] = 4;
 | 
			
		||||
                BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset), lockkey.Value);
 | 
			
		||||
                offset += 4;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // calculate crc and insert at the end of the message
 | 
			
		||||
            var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4));
 | 
			
		||||
            if (flipEndian)
 | 
			
		||||
            {
 | 
			
		||||
                Array.Reverse(crcBytes);
 | 
			
		||||
            return FinishPacket(buffer, offset);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            Buffer.BlockCopy(crcBytes, 0, message, offset, 4);
 | 
			
		||||
        internal static int WriteNullTerminatedString(Span<byte> buffer, ReadOnlySpan<char> payload)
 | 
			
		||||
        {
 | 
			
		||||
            int len = Encoding.UTF8.GetBytes(payload, buffer.Slice(1)) + 1;
 | 
			
		||||
 | 
			
		||||
            return message;
 | 
			
		||||
            // TODO: variable length: this can be 2 bytes if len > 127
 | 
			
		||||
            // Write length in front of value
 | 
			
		||||
            buffer[0] = Convert.ToByte(len);
 | 
			
		||||
 | 
			
		||||
            // null-terminate
 | 
			
		||||
            buffer[len++] = 0;
 | 
			
		||||
 | 
			
		||||
            return len;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static int InsertHeaderAndName(byte[] byteName, int messageLength, byte[] message)
 | 
			
		||||
        private static int WriteHeaderAndPayload(Span<byte> buffer, ReadOnlySpan<char> payload)
 | 
			
		||||
        {
 | 
			
		||||
            // check to see if we need to flip endiannes
 | 
			
		||||
            bool flipEndian = BitConverter.IsLittleEndian;
 | 
			
		||||
            int offset = 0;
 | 
			
		||||
            // Packet type
 | 
			
		||||
            BinaryPrimitives.WriteUInt16BigEndian(buffer, GetSetRequest);
 | 
			
		||||
 | 
			
		||||
            // create header bytes
 | 
			
		||||
            var getSetBytes = BitConverter.GetBytes(GetSetRequest);
 | 
			
		||||
            var msgLenBytes = BitConverter.GetBytes((ushort)(messageLength - 8)); // Subtrace 4 bytes for header and 4 bytes for crc
 | 
			
		||||
            // We write the payload length at the end
 | 
			
		||||
            int offset = 4;
 | 
			
		||||
 | 
			
		||||
            if (flipEndian)
 | 
			
		||||
            {
 | 
			
		||||
                Array.Reverse(getSetBytes);
 | 
			
		||||
                Array.Reverse(msgLenBytes);
 | 
			
		||||
            }
 | 
			
		||||
            // Tag
 | 
			
		||||
            buffer[offset++] = GetSetName;
 | 
			
		||||
 | 
			
		||||
            // insert header bytes into message
 | 
			
		||||
            Buffer.BlockCopy(getSetBytes, 0, message, offset, 2);
 | 
			
		||||
            offset += 2;
 | 
			
		||||
            Buffer.BlockCopy(msgLenBytes, 0, message, offset, 2);
 | 
			
		||||
            offset += 2;
 | 
			
		||||
 | 
			
		||||
            // insert tag name and length
 | 
			
		||||
            message[offset++] = GetSetName;
 | 
			
		||||
            message[offset++] = Convert.ToByte(byteName.Length);
 | 
			
		||||
 | 
			
		||||
            // insert name string
 | 
			
		||||
            Buffer.BlockCopy(byteName, 0, message, offset, byteName.Length);
 | 
			
		||||
            offset += byteName.Length;
 | 
			
		||||
            // Payload length + data
 | 
			
		||||
            int strLen = WriteNullTerminatedString(buffer.Slice(offset), payload);
 | 
			
		||||
            offset += strLen;
 | 
			
		||||
 | 
			
		||||
            return offset;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static int FinishPacket(Span<byte> buffer, int offset)
 | 
			
		||||
        {
 | 
			
		||||
            // Payload length
 | 
			
		||||
            BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), (ushort)(offset - 4));
 | 
			
		||||
 | 
			
		||||
            // calculate crc and insert at the end of the message
 | 
			
		||||
            var crc = Crc32.Compute(buffer.Slice(0, offset));
 | 
			
		||||
            BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset), crc);
 | 
			
		||||
 | 
			
		||||
            return offset + 4;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static bool ParseReturnMessage(byte[] buf, int numBytes, out string returnVal)
 | 
			
		||||
        {
 | 
			
		||||
            returnVal = string.Empty;
 | 
			
		||||
@ -442,90 +411,5 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 | 
			
		||||
            returnVal = Encoding.UTF8.GetString(buf, offset, valueLength - 1); // remove null terminator
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static class HdHomerunCrc
 | 
			
		||||
        {
 | 
			
		||||
            private static uint[] crc_table = {
 | 
			
		||||
            0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
 | 
			
		||||
            0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
 | 
			
		||||
            0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
 | 
			
		||||
            0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
 | 
			
		||||
            0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
 | 
			
		||||
            0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
 | 
			
		||||
            0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
 | 
			
		||||
            0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
 | 
			
		||||
            0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
 | 
			
		||||
            0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
 | 
			
		||||
            0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
 | 
			
		||||
            0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
 | 
			
		||||
            0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
 | 
			
		||||
            0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
 | 
			
		||||
            0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
 | 
			
		||||
            0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
 | 
			
		||||
            0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
 | 
			
		||||
            0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
 | 
			
		||||
            0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
 | 
			
		||||
            0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
 | 
			
		||||
            0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
 | 
			
		||||
            0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
 | 
			
		||||
            0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
 | 
			
		||||
            0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
 | 
			
		||||
            0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
 | 
			
		||||
            0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
 | 
			
		||||
            0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
 | 
			
		||||
            0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
 | 
			
		||||
            0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
 | 
			
		||||
            0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
 | 
			
		||||
            0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
 | 
			
		||||
            0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
 | 
			
		||||
            0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
 | 
			
		||||
            0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
 | 
			
		||||
            0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
 | 
			
		||||
            0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
 | 
			
		||||
            0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
 | 
			
		||||
            0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
 | 
			
		||||
            0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
 | 
			
		||||
            0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
 | 
			
		||||
            0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
 | 
			
		||||
            0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
 | 
			
		||||
            0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
 | 
			
		||||
            0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
 | 
			
		||||
            0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
 | 
			
		||||
            0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
 | 
			
		||||
            0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
 | 
			
		||||
            0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
 | 
			
		||||
            0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
 | 
			
		||||
            0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
 | 
			
		||||
            0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
 | 
			
		||||
            0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
 | 
			
		||||
            0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
 | 
			
		||||
            0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
 | 
			
		||||
            0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
 | 
			
		||||
            0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
 | 
			
		||||
            0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
 | 
			
		||||
            0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
 | 
			
		||||
            0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
 | 
			
		||||
            0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
 | 
			
		||||
            0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
 | 
			
		||||
            0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
 | 
			
		||||
            0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
 | 
			
		||||
            0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d };
 | 
			
		||||
 | 
			
		||||
            public static uint GetCrc32(byte[] bytes, int numBytes)
 | 
			
		||||
            {
 | 
			
		||||
                var hash = 0xffffffff;
 | 
			
		||||
                for (var i = 0; i < numBytes; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    hash = (hash >> 8) ^ crc_table[(hash ^ bytes[i]) & 0xff];
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var tmp = ~hash & 0xffffffff;
 | 
			
		||||
                var b0 = tmp & 0xff;
 | 
			
		||||
                var b1 = (tmp >> 8) & 0xff;
 | 
			
		||||
                var b2 = (tmp >> 16) & 0xff;
 | 
			
		||||
                var b3 = (tmp >> 24) & 0xff;
 | 
			
		||||
                return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -57,5 +57,27 @@
 | 
			
		||||
    "DeviceOnlineWithName": "{0} conectouse",
 | 
			
		||||
    "DeviceOfflineWithName": "{0} desconectouse",
 | 
			
		||||
    "Default": "Por defecto",
 | 
			
		||||
    "AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}"
 | 
			
		||||
    "AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}",
 | 
			
		||||
    "TaskCleanLogs": "Limpar Carpeta de Rexistros",
 | 
			
		||||
    "TaskCleanActivityLog": "Limpar Rexistro de Actividade",
 | 
			
		||||
    "TasksChannelsCategory": "Canáis de Internet",
 | 
			
		||||
    "TaskUpdatePlugins": "Actualizar Plugins",
 | 
			
		||||
    "User": "Usuario",
 | 
			
		||||
    "Undefined": "Sen definir",
 | 
			
		||||
    "TvShows": "Programas de TV",
 | 
			
		||||
    "System": "Sistema",
 | 
			
		||||
    "Sync": "Sincronizar",
 | 
			
		||||
    "SubtitleDownloadFailureFromForItem": "Fallou a descarga de subtítulos para {1} dende {0}",
 | 
			
		||||
    "StartupEmbyServerIsLoading": "O Servidor Jellyfin está cargando. Por favor, reinténteo en breve.",
 | 
			
		||||
    "Songs": "Cancións",
 | 
			
		||||
    "Shows": "Programas",
 | 
			
		||||
    "ServerNameNeedsToBeRestarted": "{0} precisa ser reiniciado",
 | 
			
		||||
    "ScheduledTaskStartedWithName": "{0} comezou",
 | 
			
		||||
    "ScheduledTaskFailedWithName": "{0} fallou",
 | 
			
		||||
    "ProviderValue": "Provedor: {0}",
 | 
			
		||||
    "PluginUpdatedWithName": "{0} foi actualizado",
 | 
			
		||||
    "PluginUninstalledWithName": "{0} foi desinstalado",
 | 
			
		||||
    "PluginInstalledWithName": "{0} foi instalado",
 | 
			
		||||
    "Playlists": "Listas de reproducción",
 | 
			
		||||
    "Photos": "Fotos"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -109,14 +109,14 @@
 | 
			
		||||
    "TasksMaintenanceCategory": "Qyzmet körsetu",
 | 
			
		||||
    "Undefined": "Anyqtalmağan",
 | 
			
		||||
    "Forced": "Mäjbürlı",
 | 
			
		||||
    "TaskDownloadMissingSubtitlesDescription": "Metaderekter teŋşelımderı negızınde joq subtitrlerdı Internetten ızdeidı.",
 | 
			
		||||
    "TaskDownloadMissingSubtitlesDescription": "Metaderekter teŋşelımderı negızınde joq subtitrlerdı İnternetten ızdeidı.",
 | 
			
		||||
    "TaskRefreshChannelsDescription": "Internet-arnalar mälımetterın jaŋğyrtady.",
 | 
			
		||||
    "TaskCleanTranscodeDescription": "Bіr künnen asqan qaita kodtau faildaryn joiady.",
 | 
			
		||||
    "TaskUpdatePluginsDescription": "Avtomatty türde jaŋartuğa teŋşelgen plaginder üşın jaŋartulardy jüktep alady jäne ornatady.",
 | 
			
		||||
    "TaskRefreshPeopleDescription": "Tasyğyşhanadağy aktörler men rejisörler metaderekterın jaŋartady.",
 | 
			
		||||
    "TaskCleanLogsDescription": "{0} künnen asqan jūrnal faildaryn joiady.",
 | 
			
		||||
    "TaskRefreshLibraryDescription": "Tasyğyşhanadağy jaŋa faildardy skanerleidі jäne metaderekterdı jaŋğyrtady.",
 | 
			
		||||
    "TaskRefreshChapterImagesDescription": "Sahnalary bar beineler üşіn nobailar jasaidy.",
 | 
			
		||||
    "TaskRefreshChapterImagesDescription": "Sahnalary bar beineler üşın nobailar jasaidy.",
 | 
			
		||||
    "TaskCleanCacheDescription": "Jüiede qajet emes keştelgen faildardy joiady.",
 | 
			
		||||
    "TaskCleanActivityLogDescription": "Äreket jūrnalyndağy teŋşelgen jasynan asqan jazbalary joiady."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -50,7 +50,7 @@
 | 
			
		||||
    "HeaderFavoriteArtists": "ศิลปินที่ชื่นชอบ",
 | 
			
		||||
    "HeaderFavoriteAlbums": "อัมบั้มที่ชื่นชอบ",
 | 
			
		||||
    "HeaderContinueWatching": "ดูต่อ",
 | 
			
		||||
    "HeaderAlbumArtists": "อัลบั้มศิลปิน",
 | 
			
		||||
    "HeaderAlbumArtists": "ศิลปินอัลบั้ม",
 | 
			
		||||
    "Genres": "ประเภท",
 | 
			
		||||
    "Folders": "โฟลเดอร์",
 | 
			
		||||
    "Favorites": "รายการโปรด",
 | 
			
		||||
@ -112,5 +112,6 @@
 | 
			
		||||
    "System": "ระบบ",
 | 
			
		||||
    "Sync": "ซิงค์",
 | 
			
		||||
    "SubtitleDownloadFailureFromForItem": "ไม่สามารถดาวน์โหลดคำบรรยายจาก {0} สำหรับ {1} ได้",
 | 
			
		||||
    "StartupEmbyServerIsLoading": "กำลังโหลดเซิร์ฟเวอร์ Jellyfin โปรดลองอีกครั้งในอีกสักครู่"
 | 
			
		||||
    "StartupEmbyServerIsLoading": "กำลังโหลดเซิร์ฟเวอร์ Jellyfin โปรดลองอีกครั้งในอีกสักครู่",
 | 
			
		||||
    "Default": "ค่าเริ่มต้น"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Localization
 | 
			
		||||
 | 
			
		||||
        private List<CultureDto> _cultures;
 | 
			
		||||
 | 
			
		||||
        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
 | 
			
		||||
        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Initializes a new instance of the <see cref="LocalizationManager" /> class.
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.Plugins
 | 
			
		||||
        private readonly ILogger<PluginManager> _logger;
 | 
			
		||||
        private readonly IApplicationHost _appHost;
 | 
			
		||||
        private readonly ServerConfiguration _config;
 | 
			
		||||
        private readonly IList<LocalPlugin> _plugins;
 | 
			
		||||
        private readonly List<LocalPlugin> _plugins;
 | 
			
		||||
        private readonly Version _minimumVersion;
 | 
			
		||||
 | 
			
		||||
        private IHttpClientFactory? _httpClientFactory;
 | 
			
		||||
@ -70,7 +70,7 @@ namespace Emby.Server.Implementations.Plugins
 | 
			
		||||
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
 | 
			
		||||
            _pluginsPath = pluginsPath;
 | 
			
		||||
            _appVersion = appVersion ?? throw new ArgumentNullException(nameof(appVersion));
 | 
			
		||||
            _jsonOptions = new JsonSerializerOptions(JsonDefaults.GetOptions())
 | 
			
		||||
            _jsonOptions = new JsonSerializerOptions(JsonDefaults.Options)
 | 
			
		||||
            {
 | 
			
		||||
                WriteIndented = true
 | 
			
		||||
            };
 | 
			
		||||
@ -94,7 +94,7 @@ namespace Emby.Server.Implementations.Plugins
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the Plugins.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public IList<LocalPlugin> Plugins => _plugins;
 | 
			
		||||
        public IReadOnlyList<LocalPlugin> Plugins => _plugins;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Returns all the assemblies.
 | 
			
		||||
@ -678,7 +678,7 @@ namespace Emby.Server.Implementations.Plugins
 | 
			
		||||
                var entry = versions[x];
 | 
			
		||||
                if (!string.Equals(lastName, entry.Name, StringComparison.OrdinalIgnoreCase))
 | 
			
		||||
                {
 | 
			
		||||
                    entry.DllFiles.AddRange(Directory.EnumerateFiles(entry.Path, "*.dll", SearchOption.AllDirectories));
 | 
			
		||||
                    entry.DllFiles = Directory.GetFiles(entry.Path, "*.dll", SearchOption.AllDirectories);
 | 
			
		||||
                    if (entry.IsEnabledAndSupported)
 | 
			
		||||
                    {
 | 
			
		||||
                        lastName = entry.Name;
 | 
			
		||||
 | 
			
		||||
@ -69,7 +69,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// The options for the json Serializer.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
 | 
			
		||||
        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Initializes a new instance of the <see cref="ScheduledTaskWorker" /> class.
 | 
			
		||||
 | 
			
		||||
@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.Updates
 | 
			
		||||
            _httpClientFactory = httpClientFactory;
 | 
			
		||||
            _config = config;
 | 
			
		||||
            _zipClient = zipClient;
 | 
			
		||||
            _jsonSerializerOptions = JsonDefaults.GetOptions();
 | 
			
		||||
            _jsonSerializerOptions = JsonDefaults.Options;
 | 
			
		||||
            _pluginManager = pluginManager;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -122,7 +122,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] int? height,
 | 
			
		||||
            [FromQuery] int? videoBitRate,
 | 
			
		||||
            [FromQuery] int? subtitleStreamIndex,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod subtitleMethod,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
 | 
			
		||||
            [FromQuery] int? maxRefFrames,
 | 
			
		||||
            [FromQuery] int? maxVideoBitDepth,
 | 
			
		||||
            [FromQuery] bool? requireAvc,
 | 
			
		||||
@ -174,7 +174,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                Height = height,
 | 
			
		||||
                VideoBitRate = videoBitRate,
 | 
			
		||||
                SubtitleStreamIndex = subtitleStreamIndex,
 | 
			
		||||
                SubtitleMethod = subtitleMethod,
 | 
			
		||||
                SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
 | 
			
		||||
                MaxRefFrames = maxRefFrames,
 | 
			
		||||
                MaxVideoBitDepth = maxVideoBitDepth,
 | 
			
		||||
                RequireAvc = requireAvc ?? true,
 | 
			
		||||
@ -287,7 +287,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] int? height,
 | 
			
		||||
            [FromQuery] int? videoBitRate,
 | 
			
		||||
            [FromQuery] int? subtitleStreamIndex,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod subtitleMethod,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
 | 
			
		||||
            [FromQuery] int? maxRefFrames,
 | 
			
		||||
            [FromQuery] int? maxVideoBitDepth,
 | 
			
		||||
            [FromQuery] bool? requireAvc,
 | 
			
		||||
@ -339,7 +339,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                Height = height,
 | 
			
		||||
                VideoBitRate = videoBitRate,
 | 
			
		||||
                SubtitleStreamIndex = subtitleStreamIndex,
 | 
			
		||||
                SubtitleMethod = subtitleMethod,
 | 
			
		||||
                SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
 | 
			
		||||
                MaxRefFrames = maxRefFrames,
 | 
			
		||||
                MaxVideoBitDepth = maxVideoBitDepth,
 | 
			
		||||
                RequireAvc = requireAvc ?? true,
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        private readonly IServerConfigurationManager _configurationManager;
 | 
			
		||||
        private readonly IMediaEncoder _mediaEncoder;
 | 
			
		||||
 | 
			
		||||
        private readonly JsonSerializerOptions _serializerOptions = JsonDefaults.GetOptions();
 | 
			
		||||
        private readonly JsonSerializerOptions _serializerOptions = JsonDefaults.Options;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Initializes a new instance of the <see cref="ConfigurationController"/> class.
 | 
			
		||||
 | 
			
		||||
@ -95,9 +95,9 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            return GetPluginPages(plugin).Select(i => new ConfigurationPageInfo(plugin.Instance, i.Item1));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private IEnumerable<Tuple<PluginPageInfo, IPlugin>> GetPluginPages(LocalPlugin? plugin)
 | 
			
		||||
        private IEnumerable<Tuple<PluginPageInfo, IPlugin>> GetPluginPages(LocalPlugin plugin)
 | 
			
		||||
        {
 | 
			
		||||
            if (plugin?.Instance is not IHasWebPages hasWebPages)
 | 
			
		||||
            if (plugin.Instance is not IHasWebPages hasWebPages)
 | 
			
		||||
            {
 | 
			
		||||
                return Enumerable.Empty<Tuple<PluginPageInfo, IPlugin>>();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -203,7 +203,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] int? height,
 | 
			
		||||
            [FromQuery] int? videoBitRate,
 | 
			
		||||
            [FromQuery] int? subtitleStreamIndex,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod subtitleMethod,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
 | 
			
		||||
            [FromQuery] int? maxRefFrames,
 | 
			
		||||
            [FromQuery] int? maxVideoBitDepth,
 | 
			
		||||
            [FromQuery] bool? requireAvc,
 | 
			
		||||
@ -218,7 +218,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? transcodeReasons,
 | 
			
		||||
            [FromQuery] int? audioStreamIndex,
 | 
			
		||||
            [FromQuery] int? videoStreamIndex,
 | 
			
		||||
            [FromQuery] EncodingContext context,
 | 
			
		||||
            [FromQuery] EncodingContext? context,
 | 
			
		||||
            [FromQuery] Dictionary<string, string> streamOptions,
 | 
			
		||||
            [FromQuery] bool enableAdaptiveBitrateStreaming = true)
 | 
			
		||||
        {
 | 
			
		||||
@ -255,7 +255,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                Height = height,
 | 
			
		||||
                VideoBitRate = videoBitRate,
 | 
			
		||||
                SubtitleStreamIndex = subtitleStreamIndex,
 | 
			
		||||
                SubtitleMethod = subtitleMethod,
 | 
			
		||||
                SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
 | 
			
		||||
                MaxRefFrames = maxRefFrames,
 | 
			
		||||
                MaxVideoBitDepth = maxVideoBitDepth,
 | 
			
		||||
                RequireAvc = requireAvc ?? true,
 | 
			
		||||
@ -270,7 +270,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                TranscodeReasons = transcodeReasons,
 | 
			
		||||
                AudioStreamIndex = audioStreamIndex,
 | 
			
		||||
                VideoStreamIndex = videoStreamIndex,
 | 
			
		||||
                Context = context,
 | 
			
		||||
                Context = context ?? EncodingContext.Streaming,
 | 
			
		||||
                StreamOptions = streamOptions,
 | 
			
		||||
                EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming
 | 
			
		||||
            };
 | 
			
		||||
@ -370,7 +370,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] int? height,
 | 
			
		||||
            [FromQuery] int? videoBitRate,
 | 
			
		||||
            [FromQuery] int? subtitleStreamIndex,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod subtitleMethod,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
 | 
			
		||||
            [FromQuery] int? maxRefFrames,
 | 
			
		||||
            [FromQuery] int? maxVideoBitDepth,
 | 
			
		||||
            [FromQuery] bool? requireAvc,
 | 
			
		||||
@ -385,7 +385,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? transcodeReasons,
 | 
			
		||||
            [FromQuery] int? audioStreamIndex,
 | 
			
		||||
            [FromQuery] int? videoStreamIndex,
 | 
			
		||||
            [FromQuery] EncodingContext context,
 | 
			
		||||
            [FromQuery] EncodingContext? context,
 | 
			
		||||
            [FromQuery] Dictionary<string, string> streamOptions,
 | 
			
		||||
            [FromQuery] bool enableAdaptiveBitrateStreaming = true)
 | 
			
		||||
        {
 | 
			
		||||
@ -422,7 +422,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                Height = height,
 | 
			
		||||
                VideoBitRate = videoBitRate,
 | 
			
		||||
                SubtitleStreamIndex = subtitleStreamIndex,
 | 
			
		||||
                SubtitleMethod = subtitleMethod,
 | 
			
		||||
                SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
 | 
			
		||||
                MaxRefFrames = maxRefFrames,
 | 
			
		||||
                MaxVideoBitDepth = maxVideoBitDepth,
 | 
			
		||||
                RequireAvc = requireAvc ?? true,
 | 
			
		||||
@ -437,7 +437,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                TranscodeReasons = transcodeReasons,
 | 
			
		||||
                AudioStreamIndex = audioStreamIndex,
 | 
			
		||||
                VideoStreamIndex = videoStreamIndex,
 | 
			
		||||
                Context = context,
 | 
			
		||||
                Context = context ?? EncodingContext.Streaming,
 | 
			
		||||
                StreamOptions = streamOptions,
 | 
			
		||||
                EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming
 | 
			
		||||
            };
 | 
			
		||||
@ -533,7 +533,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] int? height,
 | 
			
		||||
            [FromQuery] int? videoBitRate,
 | 
			
		||||
            [FromQuery] int? subtitleStreamIndex,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod subtitleMethod,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
 | 
			
		||||
            [FromQuery] int? maxRefFrames,
 | 
			
		||||
            [FromQuery] int? maxVideoBitDepth,
 | 
			
		||||
            [FromQuery] bool? requireAvc,
 | 
			
		||||
@ -548,7 +548,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? transcodeReasons,
 | 
			
		||||
            [FromQuery] int? audioStreamIndex,
 | 
			
		||||
            [FromQuery] int? videoStreamIndex,
 | 
			
		||||
            [FromQuery] EncodingContext context,
 | 
			
		||||
            [FromQuery] EncodingContext? context,
 | 
			
		||||
            [FromQuery] Dictionary<string, string> streamOptions)
 | 
			
		||||
        {
 | 
			
		||||
            var cancellationTokenSource = new CancellationTokenSource();
 | 
			
		||||
@ -585,7 +585,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                Height = height,
 | 
			
		||||
                VideoBitRate = videoBitRate,
 | 
			
		||||
                SubtitleStreamIndex = subtitleStreamIndex,
 | 
			
		||||
                SubtitleMethod = subtitleMethod,
 | 
			
		||||
                SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
 | 
			
		||||
                MaxRefFrames = maxRefFrames,
 | 
			
		||||
                MaxVideoBitDepth = maxVideoBitDepth,
 | 
			
		||||
                RequireAvc = requireAvc ?? true,
 | 
			
		||||
@ -600,7 +600,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                TranscodeReasons = transcodeReasons,
 | 
			
		||||
                AudioStreamIndex = audioStreamIndex,
 | 
			
		||||
                VideoStreamIndex = videoStreamIndex,
 | 
			
		||||
                Context = context,
 | 
			
		||||
                Context = context ?? EncodingContext.Streaming,
 | 
			
		||||
                StreamOptions = streamOptions
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
@ -698,7 +698,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] int? height,
 | 
			
		||||
            [FromQuery] int? videoBitRate,
 | 
			
		||||
            [FromQuery] int? subtitleStreamIndex,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod subtitleMethod,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
 | 
			
		||||
            [FromQuery] int? maxRefFrames,
 | 
			
		||||
            [FromQuery] int? maxVideoBitDepth,
 | 
			
		||||
            [FromQuery] bool? requireAvc,
 | 
			
		||||
@ -713,7 +713,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? transcodeReasons,
 | 
			
		||||
            [FromQuery] int? audioStreamIndex,
 | 
			
		||||
            [FromQuery] int? videoStreamIndex,
 | 
			
		||||
            [FromQuery] EncodingContext context,
 | 
			
		||||
            [FromQuery] EncodingContext? context,
 | 
			
		||||
            [FromQuery] Dictionary<string, string> streamOptions)
 | 
			
		||||
        {
 | 
			
		||||
            var cancellationTokenSource = new CancellationTokenSource();
 | 
			
		||||
@ -750,7 +750,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                Height = height,
 | 
			
		||||
                VideoBitRate = videoBitRate,
 | 
			
		||||
                SubtitleStreamIndex = subtitleStreamIndex,
 | 
			
		||||
                SubtitleMethod = subtitleMethod,
 | 
			
		||||
                SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
 | 
			
		||||
                MaxRefFrames = maxRefFrames,
 | 
			
		||||
                MaxVideoBitDepth = maxVideoBitDepth,
 | 
			
		||||
                RequireAvc = requireAvc ?? true,
 | 
			
		||||
@ -765,7 +765,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                TranscodeReasons = transcodeReasons,
 | 
			
		||||
                AudioStreamIndex = audioStreamIndex,
 | 
			
		||||
                VideoStreamIndex = videoStreamIndex,
 | 
			
		||||
                Context = context,
 | 
			
		||||
                Context = context ?? EncodingContext.Streaming,
 | 
			
		||||
                StreamOptions = streamOptions
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
@ -868,7 +868,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] int? height,
 | 
			
		||||
            [FromQuery] int? videoBitRate,
 | 
			
		||||
            [FromQuery] int? subtitleStreamIndex,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod subtitleMethod,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
 | 
			
		||||
            [FromQuery] int? maxRefFrames,
 | 
			
		||||
            [FromQuery] int? maxVideoBitDepth,
 | 
			
		||||
            [FromQuery] bool? requireAvc,
 | 
			
		||||
@ -883,7 +883,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? transcodeReasons,
 | 
			
		||||
            [FromQuery] int? audioStreamIndex,
 | 
			
		||||
            [FromQuery] int? videoStreamIndex,
 | 
			
		||||
            [FromQuery] EncodingContext context,
 | 
			
		||||
            [FromQuery] EncodingContext? context,
 | 
			
		||||
            [FromQuery] Dictionary<string, string> streamOptions)
 | 
			
		||||
        {
 | 
			
		||||
            var streamingRequest = new VideoRequestDto
 | 
			
		||||
@ -920,7 +920,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                Height = height,
 | 
			
		||||
                VideoBitRate = videoBitRate,
 | 
			
		||||
                SubtitleStreamIndex = subtitleStreamIndex,
 | 
			
		||||
                SubtitleMethod = subtitleMethod,
 | 
			
		||||
                SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
 | 
			
		||||
                MaxRefFrames = maxRefFrames,
 | 
			
		||||
                MaxVideoBitDepth = maxVideoBitDepth,
 | 
			
		||||
                RequireAvc = requireAvc ?? true,
 | 
			
		||||
@ -935,7 +935,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                TranscodeReasons = transcodeReasons,
 | 
			
		||||
                AudioStreamIndex = audioStreamIndex,
 | 
			
		||||
                VideoStreamIndex = videoStreamIndex,
 | 
			
		||||
                Context = context,
 | 
			
		||||
                Context = context ?? EncodingContext.Streaming,
 | 
			
		||||
                StreamOptions = streamOptions
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
@ -1040,7 +1040,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] int? height,
 | 
			
		||||
            [FromQuery] int? videoBitRate,
 | 
			
		||||
            [FromQuery] int? subtitleStreamIndex,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod subtitleMethod,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
 | 
			
		||||
            [FromQuery] int? maxRefFrames,
 | 
			
		||||
            [FromQuery] int? maxVideoBitDepth,
 | 
			
		||||
            [FromQuery] bool? requireAvc,
 | 
			
		||||
@ -1055,7 +1055,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? transcodeReasons,
 | 
			
		||||
            [FromQuery] int? audioStreamIndex,
 | 
			
		||||
            [FromQuery] int? videoStreamIndex,
 | 
			
		||||
            [FromQuery] EncodingContext context,
 | 
			
		||||
            [FromQuery] EncodingContext? context,
 | 
			
		||||
            [FromQuery] Dictionary<string, string> streamOptions)
 | 
			
		||||
        {
 | 
			
		||||
            var streamingRequest = new StreamingRequestDto
 | 
			
		||||
@ -1092,7 +1092,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                Height = height,
 | 
			
		||||
                VideoBitRate = videoBitRate,
 | 
			
		||||
                SubtitleStreamIndex = subtitleStreamIndex,
 | 
			
		||||
                SubtitleMethod = subtitleMethod,
 | 
			
		||||
                SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
 | 
			
		||||
                MaxRefFrames = maxRefFrames,
 | 
			
		||||
                MaxVideoBitDepth = maxVideoBitDepth,
 | 
			
		||||
                RequireAvc = requireAvc ?? true,
 | 
			
		||||
@ -1107,7 +1107,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                TranscodeReasons = transcodeReasons,
 | 
			
		||||
                AudioStreamIndex = audioStreamIndex,
 | 
			
		||||
                VideoStreamIndex = videoStreamIndex,
 | 
			
		||||
                Context = context,
 | 
			
		||||
                Context = context ?? EncodingContext.Streaming,
 | 
			
		||||
                StreamOptions = streamOptions
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -96,7 +96,9 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        [HttpDelete("Videos/ActiveEncodings")]
 | 
			
		||||
        [Authorize(Policy = Policies.DefaultAuthorization)]
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
			
		||||
        public ActionResult StopEncodingProcess([FromQuery] string deviceId, [FromQuery] string playSessionId)
 | 
			
		||||
        public ActionResult StopEncodingProcess(
 | 
			
		||||
            [FromQuery, Required] string deviceId,
 | 
			
		||||
            [FromQuery, Required] string playSessionId)
 | 
			
		||||
        {
 | 
			
		||||
            _transcodingJobHelper.KillTranscodingJobs(deviceId, playSessionId, path => true);
 | 
			
		||||
            return NoContent();
 | 
			
		||||
 | 
			
		||||
@ -392,7 +392,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromRoute, Required] Guid itemId,
 | 
			
		||||
            [FromRoute, Required] ImageType imageType,
 | 
			
		||||
            [FromRoute, Required] int imageIndex,
 | 
			
		||||
            [FromQuery] int newIndex)
 | 
			
		||||
            [FromQuery, Required] int newIndex)
 | 
			
		||||
        {
 | 
			
		||||
            var item = _libraryManager.GetItemById(itemId);
 | 
			
		||||
            if (item == null)
 | 
			
		||||
@ -741,7 +741,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        public async Task<ActionResult> GetArtistImage(
 | 
			
		||||
            [FromRoute, Required] string name,
 | 
			
		||||
            [FromRoute, Required] ImageType imageType,
 | 
			
		||||
            [FromQuery] string tag,
 | 
			
		||||
            [FromQuery] string? tag,
 | 
			
		||||
            [FromQuery] ImageFormat? format,
 | 
			
		||||
            [FromQuery] int? maxWidth,
 | 
			
		||||
            [FromQuery] int? maxHeight,
 | 
			
		||||
@ -820,7 +820,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        public async Task<ActionResult> GetGenreImage(
 | 
			
		||||
            [FromRoute, Required] string name,
 | 
			
		||||
            [FromRoute, Required] ImageType imageType,
 | 
			
		||||
            [FromQuery] string tag,
 | 
			
		||||
            [FromQuery] string? tag,
 | 
			
		||||
            [FromQuery] ImageFormat? format,
 | 
			
		||||
            [FromQuery] int? maxWidth,
 | 
			
		||||
            [FromQuery] int? maxHeight,
 | 
			
		||||
@ -900,7 +900,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromRoute, Required] string name,
 | 
			
		||||
            [FromRoute, Required] ImageType imageType,
 | 
			
		||||
            [FromRoute, Required] int imageIndex,
 | 
			
		||||
            [FromQuery] string tag,
 | 
			
		||||
            [FromQuery] string? tag,
 | 
			
		||||
            [FromQuery] ImageFormat? format,
 | 
			
		||||
            [FromQuery] int? maxWidth,
 | 
			
		||||
            [FromQuery] int? maxHeight,
 | 
			
		||||
@ -978,7 +978,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        public async Task<ActionResult> GetMusicGenreImage(
 | 
			
		||||
            [FromRoute, Required] string name,
 | 
			
		||||
            [FromRoute, Required] ImageType imageType,
 | 
			
		||||
            [FromQuery] string tag,
 | 
			
		||||
            [FromQuery] string? tag,
 | 
			
		||||
            [FromQuery] ImageFormat? format,
 | 
			
		||||
            [FromQuery] int? maxWidth,
 | 
			
		||||
            [FromQuery] int? maxHeight,
 | 
			
		||||
@ -1058,7 +1058,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromRoute, Required] string name,
 | 
			
		||||
            [FromRoute, Required] ImageType imageType,
 | 
			
		||||
            [FromRoute, Required] int imageIndex,
 | 
			
		||||
            [FromQuery] string tag,
 | 
			
		||||
            [FromQuery] string? tag,
 | 
			
		||||
            [FromQuery] ImageFormat? format,
 | 
			
		||||
            [FromQuery] int? maxWidth,
 | 
			
		||||
            [FromQuery] int? maxHeight,
 | 
			
		||||
@ -1136,7 +1136,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        public async Task<ActionResult> GetPersonImage(
 | 
			
		||||
            [FromRoute, Required] string name,
 | 
			
		||||
            [FromRoute, Required] ImageType imageType,
 | 
			
		||||
            [FromQuery] string tag,
 | 
			
		||||
            [FromQuery] string? tag,
 | 
			
		||||
            [FromQuery] ImageFormat? format,
 | 
			
		||||
            [FromQuery] int? maxWidth,
 | 
			
		||||
            [FromQuery] int? maxHeight,
 | 
			
		||||
@ -1216,7 +1216,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromRoute, Required] string name,
 | 
			
		||||
            [FromRoute, Required] ImageType imageType,
 | 
			
		||||
            [FromRoute, Required] int imageIndex,
 | 
			
		||||
            [FromQuery] string tag,
 | 
			
		||||
            [FromQuery] string? tag,
 | 
			
		||||
            [FromQuery] ImageFormat? format,
 | 
			
		||||
            [FromQuery] int? maxWidth,
 | 
			
		||||
            [FromQuery] int? maxHeight,
 | 
			
		||||
 | 
			
		||||
@ -86,7 +86,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates an instant playlist based on a given song.
 | 
			
		||||
        /// Creates an instant playlist based on a given album.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="id">The item id.</param>
 | 
			
		||||
        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
			
		||||
@ -122,7 +122,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates an instant playlist based on a given song.
 | 
			
		||||
        /// Creates an instant playlist based on a given playlist.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="id">The item id.</param>
 | 
			
		||||
        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
			
		||||
@ -158,7 +158,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates an instant playlist based on a given song.
 | 
			
		||||
        /// Creates an instant playlist based on a given genre.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="name">The genre name.</param>
 | 
			
		||||
        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
			
		||||
@ -172,7 +172,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
			
		||||
        [HttpGet("MusicGenres/{name}/InstantMix")]
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
			
		||||
        public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenre(
 | 
			
		||||
        public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreByName(
 | 
			
		||||
            [FromRoute, Required] string name,
 | 
			
		||||
            [FromQuery] Guid? userId,
 | 
			
		||||
            [FromQuery] int? limit,
 | 
			
		||||
@ -193,7 +193,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates an instant playlist based on a given song.
 | 
			
		||||
        /// Creates an instant playlist based on a given artist.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="id">The item id.</param>
 | 
			
		||||
        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
			
		||||
@ -229,7 +229,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates an instant playlist based on a given song.
 | 
			
		||||
        /// Creates an instant playlist based on a given genre.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="id">The item id.</param>
 | 
			
		||||
        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
			
		||||
@ -243,7 +243,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
			
		||||
        [HttpGet("MusicGenres/{id}/InstantMix")]
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
			
		||||
        public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenres(
 | 
			
		||||
        public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreById(
 | 
			
		||||
            [FromRoute, Required] Guid id,
 | 
			
		||||
            [FromQuery] Guid? userId,
 | 
			
		||||
            [FromQuery] int? limit,
 | 
			
		||||
@ -265,7 +265,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates an instant playlist based on a given song.
 | 
			
		||||
        /// Creates an instant playlist based on a given item.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="id">The item id.</param>
 | 
			
		||||
        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
			
		||||
@ -300,6 +300,80 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            return GetResult(items, user, limit, dtoOptions);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates an instant playlist based on a given artist.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="id">The item id.</param>
 | 
			
		||||
        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
			
		||||
        /// <param name="limit">Optional. The maximum number of records to return.</param>
 | 
			
		||||
        /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
 | 
			
		||||
        /// <param name="enableImages">Optional. Include image information in output.</param>
 | 
			
		||||
        /// <param name="enableUserData">Optional. Include user data.</param>
 | 
			
		||||
        /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
			
		||||
        /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
			
		||||
        /// <response code="200">Instant playlist returned.</response>
 | 
			
		||||
        /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
			
		||||
        [HttpGet("Artists/InstantMix")]
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
			
		||||
        [Obsolete("Use GetInstantMixFromArtists")]
 | 
			
		||||
        public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists2(
 | 
			
		||||
            [FromQuery, Required] Guid id,
 | 
			
		||||
            [FromQuery] Guid? userId,
 | 
			
		||||
            [FromQuery] int? limit,
 | 
			
		||||
            [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
 | 
			
		||||
            [FromQuery] bool? enableImages,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
 | 
			
		||||
        {
 | 
			
		||||
            return GetInstantMixFromArtists(
 | 
			
		||||
                id,
 | 
			
		||||
                userId,
 | 
			
		||||
                limit,
 | 
			
		||||
                fields,
 | 
			
		||||
                enableImages,
 | 
			
		||||
                enableUserData,
 | 
			
		||||
                imageTypeLimit,
 | 
			
		||||
                enableImageTypes);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates an instant playlist based on a given genre.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="id">The item id.</param>
 | 
			
		||||
        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
			
		||||
        /// <param name="limit">Optional. The maximum number of records to return.</param>
 | 
			
		||||
        /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
 | 
			
		||||
        /// <param name="enableImages">Optional. Include image information in output.</param>
 | 
			
		||||
        /// <param name="enableUserData">Optional. Include user data.</param>
 | 
			
		||||
        /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
			
		||||
        /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
			
		||||
        /// <response code="200">Instant playlist returned.</response>
 | 
			
		||||
        /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
			
		||||
        [HttpGet("MusicGenres/InstantMix")]
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
			
		||||
        [Obsolete("Use GetInstantMixFromMusicGenres instead")]
 | 
			
		||||
        public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreById2(
 | 
			
		||||
            [FromQuery, Required] Guid id,
 | 
			
		||||
            [FromQuery] Guid? userId,
 | 
			
		||||
            [FromQuery] int? limit,
 | 
			
		||||
            [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
 | 
			
		||||
            [FromQuery] bool? enableImages,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
 | 
			
		||||
        {
 | 
			
		||||
            return GetInstantMixFromMusicGenreById(
 | 
			
		||||
                id,
 | 
			
		||||
                userId,
 | 
			
		||||
                limit,
 | 
			
		||||
                fields,
 | 
			
		||||
                enableImages,
 | 
			
		||||
                enableUserData,
 | 
			
		||||
                imageTypeLimit,
 | 
			
		||||
                enableImageTypes);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private QueryResult<BaseItemDto> GetResult(List<BaseItem> items, User? user, int? limit, DtoOptions dtoOptions)
 | 
			
		||||
        {
 | 
			
		||||
            var list = items;
 | 
			
		||||
 | 
			
		||||
@ -344,11 +344,12 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            Directory.CreateDirectory(directory);
 | 
			
		||||
            using (var stream = result.Content)
 | 
			
		||||
            {
 | 
			
		||||
                // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
 | 
			
		||||
                await using var fileStream = new FileStream(
 | 
			
		||||
                    fullCachePath,
 | 
			
		||||
                    FileMode.Create,
 | 
			
		||||
                    FileAccess.Write,
 | 
			
		||||
                    FileShare.Read,
 | 
			
		||||
                    FileShare.None,
 | 
			
		||||
                    IODefaults.FileStreamBufferSize,
 | 
			
		||||
                    true);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -195,7 +195,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        [HttpPost("Items/{itemId}/ContentType")]
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
			
		||||
        public ActionResult UpdateItemContentType([FromRoute, Required] Guid itemId, [FromQuery] string contentType)
 | 
			
		||||
        public ActionResult UpdateItemContentType([FromRoute, Required] Guid itemId, [FromQuery] string? contentType)
 | 
			
		||||
        {
 | 
			
		||||
            var item = _libraryManager.GetItemById(itemId);
 | 
			
		||||
            if (item == null)
 | 
			
		||||
 | 
			
		||||
@ -303,7 +303,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <response code="204">Library scan started.</response>
 | 
			
		||||
        /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
			
		||||
        [HttpGet("Library/Refresh")]
 | 
			
		||||
        [HttpPost("Library/Refresh")]
 | 
			
		||||
        [Authorize(Policy = Policies.RequiresElevation)]
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
			
		||||
        public async Task<ActionResult> RefreshLibrary()
 | 
			
		||||
@ -590,15 +590,15 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Reports that new movies have been added by an external source.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="updates">A list of updated media paths.</param>
 | 
			
		||||
        /// <param name="dto">The update paths.</param>
 | 
			
		||||
        /// <response code="204">Report success.</response>
 | 
			
		||||
        /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
			
		||||
        [HttpPost("Library/Media/Updated")]
 | 
			
		||||
        [Authorize(Policy = Policies.DefaultAuthorization)]
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
			
		||||
        public ActionResult PostUpdatedMedia([FromBody, Required] MediaUpdateInfoDto[] updates)
 | 
			
		||||
        public ActionResult PostUpdatedMedia([FromBody, Required] MediaUpdateInfoDto dto)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var item in updates)
 | 
			
		||||
            foreach (var item in dto.Updates)
 | 
			
		||||
            {
 | 
			
		||||
                _libraryMonitor.ReportFileSystemChanged(item.Path);
 | 
			
		||||
            }
 | 
			
		||||
@ -777,7 +777,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
			
		||||
        public ActionResult<LibraryOptionsResultDto> GetLibraryOptionsInfo(
 | 
			
		||||
            [FromQuery] string? libraryContentType,
 | 
			
		||||
            [FromQuery] bool isNewLibrary)
 | 
			
		||||
            [FromQuery] bool isNewLibrary = false)
 | 
			
		||||
        {
 | 
			
		||||
            var result = new LibraryOptionsResultDto();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -241,23 +241,20 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Updates a media path.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="name">The name of the library.</param>
 | 
			
		||||
        /// <param name="pathInfo">The path info.</param>
 | 
			
		||||
        /// <param name="mediaPathRequestDto">The name of the library and path infos.</param>
 | 
			
		||||
        /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
			
		||||
        /// <response code="204">Media path updated.</response>
 | 
			
		||||
        /// <exception cref="ArgumentNullException">The name of the library may not be empty.</exception>
 | 
			
		||||
        [HttpPost("Paths/Update")]
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
			
		||||
        public ActionResult UpdateMediaPath(
 | 
			
		||||
            [FromQuery] string? name,
 | 
			
		||||
            [FromBody] MediaPathInfo? pathInfo)
 | 
			
		||||
        public ActionResult UpdateMediaPath([FromBody, Required] UpdateMediaPathRequestDto mediaPathRequestDto)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(name))
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(mediaPathRequestDto.Name))
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentNullException(nameof(name));
 | 
			
		||||
                throw new ArgumentNullException(nameof(mediaPathRequestDto), "Name must not be null or empty");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _libraryManager.UpdateMediaPath(name, pathInfo);
 | 
			
		||||
            _libraryManager.UpdateMediaPath(mediaPathRequestDto.Name, mediaPathRequestDto.PathInfo);
 | 
			
		||||
            return NoContent();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.ComponentModel.DataAnnotations;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using Jellyfin.Api.Constants;
 | 
			
		||||
@ -86,26 +87,19 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Sends a notification to all admins.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="url">The URL of the notification.</param>
 | 
			
		||||
        /// <param name="level">The level of the notification.</param>
 | 
			
		||||
        /// <param name="name">The name of the notification.</param>
 | 
			
		||||
        /// <param name="description">The description of the notification.</param>
 | 
			
		||||
        /// <param name="notificationDto">The notification request.</param>
 | 
			
		||||
        /// <response code="204">Notification sent.</response>
 | 
			
		||||
        /// <returns>A <cref see="NoContentResult"/>.</returns>
 | 
			
		||||
        [HttpPost("Admin")]
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
			
		||||
        public ActionResult CreateAdminNotification(
 | 
			
		||||
            [FromQuery] string? url,
 | 
			
		||||
            [FromQuery] NotificationLevel? level,
 | 
			
		||||
            [FromQuery] string name = "",
 | 
			
		||||
            [FromQuery] string description = "")
 | 
			
		||||
        public ActionResult CreateAdminNotification([FromBody, Required] AdminNotificationDto notificationDto)
 | 
			
		||||
        {
 | 
			
		||||
            var notification = new NotificationRequest
 | 
			
		||||
            {
 | 
			
		||||
                Name = name,
 | 
			
		||||
                Description = description,
 | 
			
		||||
                Url = url,
 | 
			
		||||
                Level = level ?? NotificationLevel.Normal,
 | 
			
		||||
                Name = notificationDto.Name,
 | 
			
		||||
                Description = notificationDto.Description,
 | 
			
		||||
                Url = notificationDto.Url,
 | 
			
		||||
                Level = notificationDto.NotificationLevel ?? NotificationLevel.Normal,
 | 
			
		||||
                UserIds = _userManager.Users
 | 
			
		||||
                    .Where(user => user.HasPermission(PermissionKind.IsAdministrator))
 | 
			
		||||
                    .Select(user => user.Id)
 | 
			
		||||
@ -114,7 +108,6 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            _notificationManager.SendNotification(notification, CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
            return NoContent();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -152,7 +152,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
			
		||||
        [HttpPost("Sessions/Playing/Ping")]
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
			
		||||
        public ActionResult PingPlaybackSession([FromQuery] string playSessionId)
 | 
			
		||||
        public ActionResult PingPlaybackSession([FromQuery, Required] string playSessionId)
 | 
			
		||||
        {
 | 
			
		||||
            _transcodingJobHelper.PingTranscodingJob(playSessionId, null);
 | 
			
		||||
            return NoContent();
 | 
			
		||||
@ -202,9 +202,9 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? mediaSourceId,
 | 
			
		||||
            [FromQuery] int? audioStreamIndex,
 | 
			
		||||
            [FromQuery] int? subtitleStreamIndex,
 | 
			
		||||
            [FromQuery] PlayMethod playMethod,
 | 
			
		||||
            [FromQuery] PlayMethod? playMethod,
 | 
			
		||||
            [FromQuery] string? liveStreamId,
 | 
			
		||||
            [FromQuery] string playSessionId,
 | 
			
		||||
            [FromQuery] string? playSessionId,
 | 
			
		||||
            [FromQuery] bool canSeek = false)
 | 
			
		||||
        {
 | 
			
		||||
            var playbackStartInfo = new PlaybackStartInfo
 | 
			
		||||
@ -214,7 +214,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                MediaSourceId = mediaSourceId,
 | 
			
		||||
                AudioStreamIndex = audioStreamIndex,
 | 
			
		||||
                SubtitleStreamIndex = subtitleStreamIndex,
 | 
			
		||||
                PlayMethod = playMethod,
 | 
			
		||||
                PlayMethod = playMethod ?? PlayMethod.Transcode,
 | 
			
		||||
                PlaySessionId = playSessionId,
 | 
			
		||||
                LiveStreamId = liveStreamId
 | 
			
		||||
            };
 | 
			
		||||
@ -254,10 +254,10 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] int? audioStreamIndex,
 | 
			
		||||
            [FromQuery] int? subtitleStreamIndex,
 | 
			
		||||
            [FromQuery] int? volumeLevel,
 | 
			
		||||
            [FromQuery] PlayMethod playMethod,
 | 
			
		||||
            [FromQuery] PlayMethod? playMethod,
 | 
			
		||||
            [FromQuery] string? liveStreamId,
 | 
			
		||||
            [FromQuery] string playSessionId,
 | 
			
		||||
            [FromQuery] RepeatMode repeatMode,
 | 
			
		||||
            [FromQuery] string? playSessionId,
 | 
			
		||||
            [FromQuery] RepeatMode? repeatMode,
 | 
			
		||||
            [FromQuery] bool isPaused = false,
 | 
			
		||||
            [FromQuery] bool isMuted = false)
 | 
			
		||||
        {
 | 
			
		||||
@ -271,10 +271,10 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                AudioStreamIndex = audioStreamIndex,
 | 
			
		||||
                SubtitleStreamIndex = subtitleStreamIndex,
 | 
			
		||||
                VolumeLevel = volumeLevel,
 | 
			
		||||
                PlayMethod = playMethod,
 | 
			
		||||
                PlayMethod = playMethod ?? PlayMethod.Transcode,
 | 
			
		||||
                PlaySessionId = playSessionId,
 | 
			
		||||
                LiveStreamId = liveStreamId,
 | 
			
		||||
                RepeatMode = repeatMode
 | 
			
		||||
                RepeatMode = repeatMode ?? RepeatMode.RepeatNone
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            playbackProgressInfo.PlayMethod = ValidatePlayMethod(playbackProgressInfo.PlayMethod, playbackProgressInfo.PlaySessionId);
 | 
			
		||||
@ -352,7 +352,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            return _userDataRepository.GetUserDataDto(item, user);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private PlayMethod ValidatePlayMethod(PlayMethod method, string playSessionId)
 | 
			
		||||
        private PlayMethod ValidatePlayMethod(PlayMethod method, string? playSessionId)
 | 
			
		||||
        {
 | 
			
		||||
            if (method == PlayMethod.Transcode)
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
@ -45,7 +45,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        {
 | 
			
		||||
            _installationManager = installationManager;
 | 
			
		||||
            _pluginManager = pluginManager;
 | 
			
		||||
            _serializerOptions = JsonDefaults.GetOptions();
 | 
			
		||||
            _serializerOptions = JsonDefaults.Options;
 | 
			
		||||
            _config = config;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -259,7 +259,8 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
 | 
			
		||||
            var fullCacheDirectory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid.");
 | 
			
		||||
            Directory.CreateDirectory(fullCacheDirectory);
 | 
			
		||||
            await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
 | 
			
		||||
            // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
 | 
			
		||||
            await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true);
 | 
			
		||||
            await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath));
 | 
			
		||||
 | 
			
		||||
@ -112,7 +112,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] int? maxAudioSampleRate,
 | 
			
		||||
            [FromQuery] int? maxAudioBitDepth,
 | 
			
		||||
            [FromQuery] bool? enableRemoteMedia,
 | 
			
		||||
            [FromQuery] bool breakOnNonKeyFrames,
 | 
			
		||||
            [FromQuery] bool breakOnNonKeyFrames = false,
 | 
			
		||||
            [FromQuery] bool enableRedirection = true)
 | 
			
		||||
        {
 | 
			
		||||
            var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels);
 | 
			
		||||
 | 
			
		||||
@ -198,7 +198,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] int? height,
 | 
			
		||||
            [FromQuery] int? videoBitRate,
 | 
			
		||||
            [FromQuery] int? subtitleStreamIndex,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod subtitleMethod,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
 | 
			
		||||
            [FromQuery] int? maxRefFrames,
 | 
			
		||||
            [FromQuery] int? maxVideoBitDepth,
 | 
			
		||||
            [FromQuery] bool? requireAvc,
 | 
			
		||||
@ -213,7 +213,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? transcodeReasons,
 | 
			
		||||
            [FromQuery] int? audioStreamIndex,
 | 
			
		||||
            [FromQuery] int? videoStreamIndex,
 | 
			
		||||
            [FromQuery] EncodingContext context,
 | 
			
		||||
            [FromQuery] EncodingContext? context,
 | 
			
		||||
            [FromQuery] Dictionary<string, string> streamOptions,
 | 
			
		||||
            [FromQuery] int? maxWidth,
 | 
			
		||||
            [FromQuery] int? maxHeight,
 | 
			
		||||
@ -253,7 +253,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                Height = height,
 | 
			
		||||
                VideoBitRate = videoBitRate,
 | 
			
		||||
                SubtitleStreamIndex = subtitleStreamIndex,
 | 
			
		||||
                SubtitleMethod = subtitleMethod,
 | 
			
		||||
                SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
 | 
			
		||||
                MaxRefFrames = maxRefFrames,
 | 
			
		||||
                MaxVideoBitDepth = maxVideoBitDepth,
 | 
			
		||||
                RequireAvc = requireAvc ?? true,
 | 
			
		||||
@ -268,7 +268,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                TranscodeReasons = transcodeReasons,
 | 
			
		||||
                AudioStreamIndex = audioStreamIndex,
 | 
			
		||||
                VideoStreamIndex = videoStreamIndex,
 | 
			
		||||
                Context = context,
 | 
			
		||||
                Context = context ?? EncodingContext.Streaming,
 | 
			
		||||
                StreamOptions = streamOptions,
 | 
			
		||||
                MaxHeight = maxHeight,
 | 
			
		||||
                MaxWidth = maxWidth,
 | 
			
		||||
 | 
			
		||||
@ -217,9 +217,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                return BadRequest("Please supply at least two videos to merge.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var videosWithVersions = items.Where(i => i.MediaSourceCount > 1).ToList();
 | 
			
		||||
 | 
			
		||||
            var primaryVersion = videosWithVersions.FirstOrDefault();
 | 
			
		||||
            var primaryVersion = items.FirstOrDefault(i => i.MediaSourceCount > 1 && string.IsNullOrEmpty(i.PrimaryVersionId));
 | 
			
		||||
            if (primaryVersion == null)
 | 
			
		||||
            {
 | 
			
		||||
                primaryVersion = items
 | 
			
		||||
@ -364,7 +362,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] int? height,
 | 
			
		||||
            [FromQuery] int? videoBitRate,
 | 
			
		||||
            [FromQuery] int? subtitleStreamIndex,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod subtitleMethod,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
 | 
			
		||||
            [FromQuery] int? maxRefFrames,
 | 
			
		||||
            [FromQuery] int? maxVideoBitDepth,
 | 
			
		||||
            [FromQuery] bool? requireAvc,
 | 
			
		||||
@ -379,7 +377,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? transcodeReasons,
 | 
			
		||||
            [FromQuery] int? audioStreamIndex,
 | 
			
		||||
            [FromQuery] int? videoStreamIndex,
 | 
			
		||||
            [FromQuery] EncodingContext context,
 | 
			
		||||
            [FromQuery] EncodingContext? context,
 | 
			
		||||
            [FromQuery] Dictionary<string, string> streamOptions)
 | 
			
		||||
        {
 | 
			
		||||
            var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head;
 | 
			
		||||
@ -418,7 +416,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                Height = height,
 | 
			
		||||
                VideoBitRate = videoBitRate,
 | 
			
		||||
                SubtitleStreamIndex = subtitleStreamIndex,
 | 
			
		||||
                SubtitleMethod = subtitleMethod,
 | 
			
		||||
                SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
 | 
			
		||||
                MaxRefFrames = maxRefFrames,
 | 
			
		||||
                MaxVideoBitDepth = maxVideoBitDepth,
 | 
			
		||||
                RequireAvc = requireAvc ?? true,
 | 
			
		||||
@ -433,7 +431,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                TranscodeReasons = transcodeReasons,
 | 
			
		||||
                AudioStreamIndex = audioStreamIndex,
 | 
			
		||||
                VideoStreamIndex = videoStreamIndex,
 | 
			
		||||
                Context = context,
 | 
			
		||||
                Context = context ?? EncodingContext.Streaming,
 | 
			
		||||
                StreamOptions = streamOptions
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
@ -620,7 +618,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] int? height,
 | 
			
		||||
            [FromQuery] int? videoBitRate,
 | 
			
		||||
            [FromQuery] int? subtitleStreamIndex,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod subtitleMethod,
 | 
			
		||||
            [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
 | 
			
		||||
            [FromQuery] int? maxRefFrames,
 | 
			
		||||
            [FromQuery] int? maxVideoBitDepth,
 | 
			
		||||
            [FromQuery] bool? requireAvc,
 | 
			
		||||
@ -635,7 +633,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? transcodeReasons,
 | 
			
		||||
            [FromQuery] int? audioStreamIndex,
 | 
			
		||||
            [FromQuery] int? videoStreamIndex,
 | 
			
		||||
            [FromQuery] EncodingContext context,
 | 
			
		||||
            [FromQuery] EncodingContext? context,
 | 
			
		||||
            [FromQuery] Dictionary<string, string> streamOptions)
 | 
			
		||||
        {
 | 
			
		||||
            return GetVideoStream(
 | 
			
		||||
 | 
			
		||||
@ -107,7 +107,8 @@ namespace Jellyfin.Api.Helpers
 | 
			
		||||
            // Headers only
 | 
			
		||||
            if (isHeadRequest)
 | 
			
		||||
            {
 | 
			
		||||
                return new FileContentResult(Array.Empty<byte>(), contentType);
 | 
			
		||||
                httpContext.Response.Headers[HeaderNames.ContentType] = contentType;
 | 
			
		||||
                return new OkResult();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var transcodingLock = transcodingJobHelper.GetTranscodingLock(outputPath);
 | 
			
		||||
 | 
			
		||||
@ -508,17 +508,15 @@ namespace Jellyfin.Api.Helpers
 | 
			
		||||
 | 
			
		||||
        private static void ApplyDeviceProfileSettings(StreamState state, IDlnaManager dlnaManager, IDeviceManager deviceManager, HttpRequest request, string? deviceProfileId, bool? @static)
 | 
			
		||||
        {
 | 
			
		||||
            var headers = request.Headers;
 | 
			
		||||
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(deviceProfileId))
 | 
			
		||||
            {
 | 
			
		||||
                state.DeviceProfile = dlnaManager.GetProfile(deviceProfileId);
 | 
			
		||||
            }
 | 
			
		||||
            else if (!string.IsNullOrWhiteSpace(deviceProfileId))
 | 
			
		||||
 | 
			
		||||
                if (state.DeviceProfile == null)
 | 
			
		||||
                {
 | 
			
		||||
                    var caps = deviceManager.GetCapabilities(deviceProfileId);
 | 
			
		||||
 | 
			
		||||
                state.DeviceProfile = caps == null ? dlnaManager.GetProfile(headers) : caps.DeviceProfile;
 | 
			
		||||
                    state.DeviceProfile = caps == null ? dlnaManager.GetProfile(request.Headers) : caps.DeviceProfile;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var profile = state.DeviceProfile;
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,6 @@
 | 
			
		||||
 | 
			
		||||
  <!-- Code Analyzers-->
 | 
			
		||||
  <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
 | 
			
		||||
    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,7 @@
 | 
			
		||||
namespace Jellyfin.Api.Models.LibraryDtos
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
namespace Jellyfin.Api.Models.LibraryDtos
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Media Update Info Dto.
 | 
			
		||||
@ -6,14 +9,8 @@
 | 
			
		||||
    public class MediaUpdateInfoDto
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets media path.
 | 
			
		||||
        /// Gets or sets the list of updates.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string? Path { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets media update type.
 | 
			
		||||
        /// Created, Modified, Deleted.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string? UpdateType { get; set; }
 | 
			
		||||
        public IReadOnlyList<MediaUpdateInfoPathDto> Updates { get; set; } = Array.Empty<MediaUpdateInfoPathDto>();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										19
									
								
								Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
namespace Jellyfin.Api.Models.LibraryDtos
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// The media update info path.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class MediaUpdateInfoPathDto
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets media path.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string? Path { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets media update type.
 | 
			
		||||
        /// Created, Modified, Deleted.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string? UpdateType { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
using System.ComponentModel.DataAnnotations;
 | 
			
		||||
using MediaBrowser.Model.Configuration;
 | 
			
		||||
 | 
			
		||||
namespace Jellyfin.Api.Models.LibraryStructureDto
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Update library options dto.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class UpdateMediaPathRequestDto
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets the library name.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [Required]
 | 
			
		||||
        public string Name { get; set; } = null!;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets library folder path information.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [Required]
 | 
			
		||||
        public MediaPathInfo PathInfo { get; set; } = null!;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								Jellyfin.Api/Models/NotificationDtos/AdminNotificationDto.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Jellyfin.Api/Models/NotificationDtos/AdminNotificationDto.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
using MediaBrowser.Model.Notifications;
 | 
			
		||||
 | 
			
		||||
namespace Jellyfin.Api.Models.NotificationDtos
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// The admin notification dto.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class AdminNotificationDto
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets the notification name.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string? Name { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets the notification description.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string? Description { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets the notification level.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public NotificationLevel? NotificationLevel { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets the notification url.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string? Url { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,67 +1,21 @@
 | 
			
		||||
#pragma warning disable CS1591
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using Jellyfin.Data.Enums;
 | 
			
		||||
 | 
			
		||||
namespace Jellyfin.Data
 | 
			
		||||
{
 | 
			
		||||
    public static class DayOfWeekHelper
 | 
			
		||||
    {
 | 
			
		||||
        public static List<DayOfWeek> GetDaysOfWeek(DynamicDayOfWeek day)
 | 
			
		||||
        public static DayOfWeek[] GetDaysOfWeek(DynamicDayOfWeek day)
 | 
			
		||||
        {
 | 
			
		||||
            var days = new List<DayOfWeek>(7);
 | 
			
		||||
 | 
			
		||||
            if (day == DynamicDayOfWeek.Sunday
 | 
			
		||||
                || day == DynamicDayOfWeek.Weekend
 | 
			
		||||
                || day == DynamicDayOfWeek.Everyday)
 | 
			
		||||
            return day switch
 | 
			
		||||
            {
 | 
			
		||||
                days.Add(DayOfWeek.Sunday);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (day == DynamicDayOfWeek.Monday
 | 
			
		||||
                || day == DynamicDayOfWeek.Weekday
 | 
			
		||||
                || day == DynamicDayOfWeek.Everyday)
 | 
			
		||||
            {
 | 
			
		||||
                days.Add(DayOfWeek.Monday);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (day == DynamicDayOfWeek.Tuesday
 | 
			
		||||
                || day == DynamicDayOfWeek.Weekday
 | 
			
		||||
                || day == DynamicDayOfWeek.Everyday)
 | 
			
		||||
            {
 | 
			
		||||
                days.Add(DayOfWeek.Tuesday);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (day == DynamicDayOfWeek.Wednesday
 | 
			
		||||
                || day == DynamicDayOfWeek.Weekday
 | 
			
		||||
                || day == DynamicDayOfWeek.Everyday)
 | 
			
		||||
            {
 | 
			
		||||
                days.Add(DayOfWeek.Wednesday);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (day == DynamicDayOfWeek.Thursday
 | 
			
		||||
                || day == DynamicDayOfWeek.Weekday
 | 
			
		||||
                || day == DynamicDayOfWeek.Everyday)
 | 
			
		||||
            {
 | 
			
		||||
                days.Add(DayOfWeek.Thursday);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (day == DynamicDayOfWeek.Friday
 | 
			
		||||
                || day == DynamicDayOfWeek.Weekday
 | 
			
		||||
                || day == DynamicDayOfWeek.Everyday)
 | 
			
		||||
            {
 | 
			
		||||
                days.Add(DayOfWeek.Friday);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (day == DynamicDayOfWeek.Saturday
 | 
			
		||||
                || day == DynamicDayOfWeek.Weekend
 | 
			
		||||
                || day == DynamicDayOfWeek.Everyday)
 | 
			
		||||
            {
 | 
			
		||||
                days.Add(DayOfWeek.Saturday);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return days;
 | 
			
		||||
                DynamicDayOfWeek.Everyday => new[] { DayOfWeek.Sunday, DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday, DayOfWeek.Saturday },
 | 
			
		||||
                DynamicDayOfWeek.Weekday => new[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday },
 | 
			
		||||
                DynamicDayOfWeek.Weekend => new[] { DayOfWeek.Sunday, DayOfWeek.Saturday },
 | 
			
		||||
                _ => new[] { (DayOfWeek)day }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
 | 
			
		||||
#pragma warning disable CA2227
 | 
			
		||||
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.ComponentModel.DataAnnotations;
 | 
			
		||||
using System.ComponentModel.DataAnnotations.Schema;
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
 | 
			
		||||
 | 
			
		||||
using System.ComponentModel.DataAnnotations;
 | 
			
		||||
using System.ComponentModel.DataAnnotations.Schema;
 | 
			
		||||
using Jellyfin.Data.Enums;
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,8 @@
 | 
			
		||||
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
 | 
			
		||||
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
 | 
			
		||||
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
 | 
			
		||||
    <AnalysisMode>AllEnabledByDefault</AnalysisMode>
 | 
			
		||||
    <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
 | 
			
		||||
    <PublishRepositoryUrl>true</PublishRepositoryUrl>
 | 
			
		||||
    <EmbedUntrackedSources>true</EmbedUntrackedSources>
 | 
			
		||||
    <IncludeSymbols>true</IncludeSymbols>
 | 
			
		||||
@ -24,17 +26,12 @@
 | 
			
		||||
    <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
 | 
			
		||||
  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
 | 
			
		||||
    <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <!-- Code analysers-->
 | 
			
		||||
  <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
 | 
			
		||||
    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,8 @@
 | 
			
		||||
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
 | 
			
		||||
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
 | 
			
		||||
    <Nullable>enable</Nullable>
 | 
			
		||||
    <AnalysisMode>AllEnabledByDefault</AnalysisMode>
 | 
			
		||||
    <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
@ -30,16 +32,16 @@
 | 
			
		||||
    <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <!-- Needed for https://github.com/dotnet/roslyn-analyzers/issues/4382 which is in the SDK yet -->
 | 
			
		||||
    <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3" PrivateAssets="All" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <!-- Code analysers-->
 | 
			
		||||
  <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
 | 
			
		||||
    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
 | 
			
		||||
    <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
 | 
			
		||||
</Project>
 | 
			
		||||
 | 
			
		||||
@ -274,8 +274,8 @@ namespace Jellyfin.Drawing.Skia
 | 
			
		||||
 | 
			
		||||
            if (requiresTransparencyHack || forceCleanBitmap)
 | 
			
		||||
            {
 | 
			
		||||
                using var codec = SKCodec.Create(NormalizePath(path));
 | 
			
		||||
                if (codec == null)
 | 
			
		||||
                using SKCodec codec = SKCodec.Create(NormalizePath(path), out SKCodecResult res);
 | 
			
		||||
                if (res != SKCodecResult.Success)
 | 
			
		||||
                {
 | 
			
		||||
                    origin = GetSKEncodedOrigin(orientation);
 | 
			
		||||
                    return null;
 | 
			
		||||
@ -345,11 +345,6 @@ namespace Jellyfin.Drawing.Skia
 | 
			
		||||
 | 
			
		||||
        private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin)
 | 
			
		||||
        {
 | 
			
		||||
            if (origin == SKEncodedOrigin.Default)
 | 
			
		||||
            {
 | 
			
		||||
                return bitmap;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var needsFlip = origin == SKEncodedOrigin.LeftBottom
 | 
			
		||||
                            || origin == SKEncodedOrigin.LeftTop
 | 
			
		||||
                            || origin == SKEncodedOrigin.RightBottom
 | 
			
		||||
@ -447,7 +442,7 @@ namespace Jellyfin.Drawing.Skia
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc/>
 | 
			
		||||
        public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat)
 | 
			
		||||
        public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat)
 | 
			
		||||
        {
 | 
			
		||||
            if (inputPath.Length == 0)
 | 
			
		||||
            {
 | 
			
		||||
@ -459,7 +454,7 @@ namespace Jellyfin.Drawing.Skia
 | 
			
		||||
                throw new ArgumentException("String can't be empty.", nameof(outputPath));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var skiaOutputFormat = GetImageFormat(selectedOutputFormat);
 | 
			
		||||
            var skiaOutputFormat = GetImageFormat(outputFormat);
 | 
			
		||||
 | 
			
		||||
            var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor);
 | 
			
		||||
            var hasForegroundColor = !string.IsNullOrWhiteSpace(options.ForegroundLayer);
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,8 @@
 | 
			
		||||
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
 | 
			
		||||
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
 | 
			
		||||
    <Nullable>enable</Nullable>
 | 
			
		||||
    <AnalysisMode>AllEnabledByDefault</AnalysisMode>
 | 
			
		||||
    <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
@ -13,16 +15,11 @@
 | 
			
		||||
 | 
			
		||||
  <!-- Code Analyzers-->
 | 
			
		||||
  <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
 | 
			
		||||
    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
 | 
			
		||||
    <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
 | 
			
		||||
    <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
 | 
			
		||||
 | 
			
		||||
@ -285,14 +285,25 @@ namespace Jellyfin.Networking.Manager
 | 
			
		||||
                // No bind address and no exclusions, so listen on all interfaces.
 | 
			
		||||
                Collection<IPObject> result = new Collection<IPObject>();
 | 
			
		||||
 | 
			
		||||
                if (IsIP4Enabled)
 | 
			
		||||
                if (IsIP6Enabled && IsIP4Enabled)
 | 
			
		||||
                {
 | 
			
		||||
                    // Kestrel source code shows it uses Sockets.DualMode - so this also covers IPAddress.Any
 | 
			
		||||
                    result.AddItem(IPAddress.IPv6Any);
 | 
			
		||||
                }
 | 
			
		||||
                else if (IsIP4Enabled)
 | 
			
		||||
                {
 | 
			
		||||
                    result.AddItem(IPAddress.Any);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (IsIP6Enabled)
 | 
			
		||||
                else if (IsIP6Enabled)
 | 
			
		||||
                {
 | 
			
		||||
                    result.AddItem(IPAddress.IPv6Any);
 | 
			
		||||
                    // Cannot use IPv6Any as Kestrel will bind to IPv4 addresses.
 | 
			
		||||
                    foreach (var iface in _interfaceAddresses)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (iface.AddressFamily == AddressFamily.InterNetworkV6)
 | 
			
		||||
                        {
 | 
			
		||||
                            result.AddItem(iface.Address);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return result;
 | 
			
		||||
@ -425,7 +436,7 @@ namespace Jellyfin.Networking.Manager
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // There isn't any others, so we'll use the loopback.
 | 
			
		||||
            result = IsIP6Enabled ? "::" : "127.0.0.1";
 | 
			
		||||
            result = IsIP6Enabled ? "::1" : "127.0.0.1";
 | 
			
		||||
            _logger.LogWarning("{Source}: GetBindInterface: Loopback {Result} returned.", source, result);
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -29,20 +29,20 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Security
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        public async Task OnEvent(GenericEventArgs<AuthenticationResult> e)
 | 
			
		||||
        public async Task OnEvent(GenericEventArgs<AuthenticationResult> eventArgs)
 | 
			
		||||
        {
 | 
			
		||||
            await _activityManager.CreateAsync(new ActivityLog(
 | 
			
		||||
                string.Format(
 | 
			
		||||
                    CultureInfo.InvariantCulture,
 | 
			
		||||
                    _localizationManager.GetLocalizedString("AuthenticationSucceededWithUserName"),
 | 
			
		||||
                    e.Argument.User.Name),
 | 
			
		||||
                    eventArgs.Argument.User.Name),
 | 
			
		||||
                "AuthenticationSucceeded",
 | 
			
		||||
                e.Argument.User.Id)
 | 
			
		||||
                eventArgs.Argument.User.Id)
 | 
			
		||||
            {
 | 
			
		||||
                ShortOverview = string.Format(
 | 
			
		||||
                    CultureInfo.InvariantCulture,
 | 
			
		||||
                    _localizationManager.GetLocalizedString("LabelIpAddressValue"),
 | 
			
		||||
                    e.Argument.SessionInfo.RemoteEndPoint),
 | 
			
		||||
                    eventArgs.Argument.SessionInfo.RemoteEndPoint),
 | 
			
		||||
            }).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -33,10 +33,10 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.System
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        public async Task OnEvent(TaskCompletionEventArgs e)
 | 
			
		||||
        public async Task OnEvent(TaskCompletionEventArgs eventArgs)
 | 
			
		||||
        {
 | 
			
		||||
            var result = e.Result;
 | 
			
		||||
            var task = e.Task;
 | 
			
		||||
            var result = eventArgs.Result;
 | 
			
		||||
            var task = eventArgs.Task;
 | 
			
		||||
 | 
			
		||||
            if (task.ScheduledTask is IConfigurableScheduledTask activityTask
 | 
			
		||||
                && !activityTask.IsLogged)
 | 
			
		||||
@ -54,14 +54,14 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.System
 | 
			
		||||
            {
 | 
			
		||||
                var vals = new List<string>();
 | 
			
		||||
 | 
			
		||||
                if (!string.IsNullOrEmpty(e.Result.ErrorMessage))
 | 
			
		||||
                if (!string.IsNullOrEmpty(eventArgs.Result.ErrorMessage))
 | 
			
		||||
                {
 | 
			
		||||
                    vals.Add(e.Result.ErrorMessage);
 | 
			
		||||
                    vals.Add(eventArgs.Result.ErrorMessage);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!string.IsNullOrEmpty(e.Result.LongErrorMessage))
 | 
			
		||||
                if (!string.IsNullOrEmpty(eventArgs.Result.LongErrorMessage))
 | 
			
		||||
                {
 | 
			
		||||
                    vals.Add(e.Result.LongErrorMessage);
 | 
			
		||||
                    vals.Add(eventArgs.Result.LongErrorMessage);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                await _activityManager.CreateAsync(new ActivityLog(
 | 
			
		||||
 | 
			
		||||
@ -30,13 +30,13 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        public async Task OnEvent(PluginUninstalledEventArgs e)
 | 
			
		||||
        public async Task OnEvent(PluginUninstalledEventArgs eventArgs)
 | 
			
		||||
        {
 | 
			
		||||
            await _activityManager.CreateAsync(new ActivityLog(
 | 
			
		||||
                    string.Format(
 | 
			
		||||
                        CultureInfo.InvariantCulture,
 | 
			
		||||
                        _localizationManager.GetLocalizedString("PluginUninstalledWithName"),
 | 
			
		||||
                        e.Argument.Name),
 | 
			
		||||
                        eventArgs.Argument.Name),
 | 
			
		||||
                    NotificationType.PluginUninstalled.ToString(),
 | 
			
		||||
                    Guid.Empty))
 | 
			
		||||
                .ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
@ -30,12 +30,12 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Users
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        public async Task OnEvent(UserUpdatedEventArgs e)
 | 
			
		||||
        public async Task OnEvent(UserUpdatedEventArgs eventArgs)
 | 
			
		||||
        {
 | 
			
		||||
            await _sessionManager.SendMessageToUserSessions(
 | 
			
		||||
                new List<Guid> { e.Argument.Id },
 | 
			
		||||
                new List<Guid> { eventArgs.Argument.Id },
 | 
			
		||||
                SessionMessageType.UserUpdated,
 | 
			
		||||
                _userManager.GetUserDto(e.Argument),
 | 
			
		||||
                _userManager.GetUserDto(eventArgs.Argument),
 | 
			
		||||
                CancellationToken.None).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,8 @@
 | 
			
		||||
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
 | 
			
		||||
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
 | 
			
		||||
    <Nullable>enable</Nullable>
 | 
			
		||||
    <AnalysisMode>AllEnabledByDefault</AnalysisMode>
 | 
			
		||||
    <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
 | 
			
		||||
  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
 | 
			
		||||
@ -14,7 +16,6 @@
 | 
			
		||||
 | 
			
		||||
  <!-- Code analysers-->
 | 
			
		||||
  <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
 | 
			
		||||
    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
 | 
			
		||||
 | 
			
		||||
@ -66,7 +66,7 @@ namespace Jellyfin.Server.Implementations.Users
 | 
			
		||||
                else if (string.Equals(
 | 
			
		||||
                    spr.Pin.Replace("-", string.Empty, StringComparison.Ordinal),
 | 
			
		||||
                    pin.Replace("-", string.Empty, StringComparison.Ordinal),
 | 
			
		||||
                    StringComparison.InvariantCultureIgnoreCase))
 | 
			
		||||
                    StringComparison.OrdinalIgnoreCase))
 | 
			
		||||
                {
 | 
			
		||||
                    var resetUser = userManager.GetUserByName(spr.UserName)
 | 
			
		||||
                        ?? throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found");
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
#pragma warning disable CA1307
 | 
			
		||||
#pragma warning disable CA1309
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ using MediaBrowser.Controller.Net;
 | 
			
		||||
using MediaBrowser.Model.Activity;
 | 
			
		||||
using MediaBrowser.Model.IO;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using Microsoft.Extensions.Configuration;
 | 
			
		||||
using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
using Microsoft.Extensions.Logging;
 | 
			
		||||
 | 
			
		||||
@ -36,18 +37,21 @@ namespace Jellyfin.Server
 | 
			
		||||
        /// <param name="applicationPaths">The <see cref="ServerApplicationPaths" /> to be used by the <see cref="CoreAppHost" />.</param>
 | 
			
		||||
        /// <param name="loggerFactory">The <see cref="ILoggerFactory" /> to be used by the <see cref="CoreAppHost" />.</param>
 | 
			
		||||
        /// <param name="options">The <see cref="StartupOptions" /> to be used by the <see cref="CoreAppHost" />.</param>
 | 
			
		||||
        /// <param name="startupConfig">The <see cref="IConfiguration" /> to be used by the <see cref="CoreAppHost" />.</param>
 | 
			
		||||
        /// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param>
 | 
			
		||||
        /// <param name="collection">The <see cref="IServiceCollection"/> to be used by the <see cref="CoreAppHost"/>.</param>
 | 
			
		||||
        public CoreAppHost(
 | 
			
		||||
            IServerApplicationPaths applicationPaths,
 | 
			
		||||
            ILoggerFactory loggerFactory,
 | 
			
		||||
            IStartupOptions options,
 | 
			
		||||
            IConfiguration startupConfig,
 | 
			
		||||
            IFileSystem fileSystem,
 | 
			
		||||
            IServiceCollection collection)
 | 
			
		||||
            : base(
 | 
			
		||||
                applicationPaths,
 | 
			
		||||
                loggerFactory,
 | 
			
		||||
                options,
 | 
			
		||||
                startupConfig,
 | 
			
		||||
                fileSystem,
 | 
			
		||||
                collection)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
@ -225,14 +225,13 @@ namespace Jellyfin.Server.Extensions
 | 
			
		||||
                .AddJsonOptions(options =>
 | 
			
		||||
                {
 | 
			
		||||
                    // Update all properties that are set in JsonDefaults
 | 
			
		||||
                    var jsonOptions = JsonDefaults.GetPascalCaseOptions();
 | 
			
		||||
                    var jsonOptions = JsonDefaults.PascalCaseOptions;
 | 
			
		||||
 | 
			
		||||
                    // From JsonDefaults
 | 
			
		||||
                    options.JsonSerializerOptions.ReadCommentHandling = jsonOptions.ReadCommentHandling;
 | 
			
		||||
                    options.JsonSerializerOptions.WriteIndented = jsonOptions.WriteIndented;
 | 
			
		||||
                    options.JsonSerializerOptions.DefaultIgnoreCondition = jsonOptions.DefaultIgnoreCondition;
 | 
			
		||||
                    options.JsonSerializerOptions.NumberHandling = jsonOptions.NumberHandling;
 | 
			
		||||
                    options.JsonSerializerOptions.PropertyNameCaseInsensitive = jsonOptions.PropertyNameCaseInsensitive;
 | 
			
		||||
 | 
			
		||||
                    options.JsonSerializerOptions.Converters.Clear();
 | 
			
		||||
                    foreach (var converter in jsonOptions.Converters)
 | 
			
		||||
 | 
			
		||||
@ -25,6 +25,8 @@ namespace Jellyfin.Server.Filters
 | 
			
		||||
            context.SchemaGenerator.GenerateSchema(typeof(GeneralCommandType), context.SchemaRepository);
 | 
			
		||||
 | 
			
		||||
            context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<object>), context.SchemaRepository);
 | 
			
		||||
 | 
			
		||||
            context.SchemaGenerator.GenerateSchema(typeof(SessionMessageType), context.SchemaRepository);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@ namespace Jellyfin.Server.Formatters
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Initializes a new instance of the <see cref="CamelCaseJsonProfileFormatter"/> class.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public CamelCaseJsonProfileFormatter() : base(JsonDefaults.GetCamelCaseOptions())
 | 
			
		||||
        public CamelCaseJsonProfileFormatter() : base(JsonDefaults.CamelCaseOptions)
 | 
			
		||||
        {
 | 
			
		||||
            SupportedMediaTypes.Clear();
 | 
			
		||||
            SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(JsonDefaults.CamelCaseMediaType));
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@ namespace Jellyfin.Server.Formatters
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Initializes a new instance of the <see cref="PascalCaseJsonProfileFormatter"/> class.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public PascalCaseJsonProfileFormatter() : base(JsonDefaults.GetPascalCaseOptions())
 | 
			
		||||
        public PascalCaseJsonProfileFormatter() : base(JsonDefaults.PascalCaseOptions)
 | 
			
		||||
        {
 | 
			
		||||
            SupportedMediaTypes.Clear();
 | 
			
		||||
            // Add application/json for default formatter
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,9 @@
 | 
			
		||||
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
 | 
			
		||||
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
 | 
			
		||||
    <Nullable>enable</Nullable>
 | 
			
		||||
    <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
 | 
			
		||||
    <AnalysisMode>AllEnabledByDefault</AnalysisMode>
 | 
			
		||||
    <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
 | 
			
		||||
    <!-- <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> -->
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
@ -26,16 +28,11 @@
 | 
			
		||||
 | 
			
		||||
  <!-- Code Analyzers-->
 | 
			
		||||
  <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
 | 
			
		||||
    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
 | 
			
		||||
    <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <PackageReference Include="CommandLineParser" Version="2.8.0" />
 | 
			
		||||
    <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
 | 
			
		||||
 | 
			
		||||
@ -16,9 +16,12 @@ namespace Jellyfin.Server.Migrations
 | 
			
		||||
            Applied = new List<(Guid Id, string Name)>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
// .Net xml serializer can't handle interfaces
 | 
			
		||||
#pragma warning disable CA1002 // Do not expose generic lists
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the list of applied migration routine names.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public List<(Guid Id, string Name)> Applied { get; }
 | 
			
		||||
#pragma warning restore CA1002
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -76,7 +76,7 @@ namespace Jellyfin.Server.Migrations.Routines
 | 
			
		||||
 | 
			
		||||
                foreach (var entry in queryResult)
 | 
			
		||||
                {
 | 
			
		||||
                    UserMockup? mockup = JsonSerializer.Deserialize<UserMockup>(entry[2].ToBlob(), JsonDefaults.GetOptions());
 | 
			
		||||
                    UserMockup? mockup = JsonSerializer.Deserialize<UserMockup>(entry[2].ToBlob(), JsonDefaults.Options);
 | 
			
		||||
                    if (mockup == null)
 | 
			
		||||
                    {
 | 
			
		||||
                        continue;
 | 
			
		||||
 | 
			
		||||
@ -164,6 +164,7 @@ namespace Jellyfin.Server
 | 
			
		||||
                appPaths,
 | 
			
		||||
                _loggerFactory,
 | 
			
		||||
                options,
 | 
			
		||||
                startupConfig,
 | 
			
		||||
                new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
 | 
			
		||||
                serviceCollection);
 | 
			
		||||
 | 
			
		||||
@ -221,7 +222,7 @@ namespace Jellyfin.Server
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                appHost?.Dispose();
 | 
			
		||||
                appHost.Dispose();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (_restartOnShutdown)
 | 
			
		||||
@ -280,7 +281,7 @@ namespace Jellyfin.Server
 | 
			
		||||
                    bool flagged = false;
 | 
			
		||||
                    foreach (IPObject netAdd in addresses)
 | 
			
		||||
                    {
 | 
			
		||||
                        _logger.LogInformation("Kestrel listening on {0}", netAdd);
 | 
			
		||||
                        _logger.LogInformation("Kestrel listening on {Address}", netAdd.Address == IPAddress.IPv6Any ? "All Addresses" : netAdd);
 | 
			
		||||
                        options.Listen(netAdd.Address, appHost.HttpPort);
 | 
			
		||||
                        if (appHost.ListenWithHttps)
 | 
			
		||||
                        {
 | 
			
		||||
@ -622,7 +623,7 @@ namespace Jellyfin.Server
 | 
			
		||||
            string commandLineArgsString;
 | 
			
		||||
            if (options.RestartArgs != null)
 | 
			
		||||
            {
 | 
			
		||||
                commandLineArgsString = options.RestartArgs ?? string.Empty;
 | 
			
		||||
                commandLineArgsString = options.RestartArgs;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
@ -21,4 +21,4 @@ using System.Runtime.InteropServices;
 | 
			
		||||
// COM, set the ComVisible attribute to true on that type.
 | 
			
		||||
[assembly: ComVisible(false)]
 | 
			
		||||
 | 
			
		||||
[assembly: InternalsVisibleTo("Jellyfin.Api.Tests")]
 | 
			
		||||
[assembly: InternalsVisibleTo("Jellyfin.Server.Tests")]
 | 
			
		||||
 | 
			
		||||
@ -77,7 +77,7 @@ namespace Jellyfin.Server
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        [Option("published-server-url", Required = false, HelpText = "Jellyfin Server URL to publish via auto discover process")]
 | 
			
		||||
        public Uri? PublishedServerUrl { get; set; }
 | 
			
		||||
        public string? PublishedServerUrl { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the command line options as a dictionary that can be used in the .NET configuration system.
 | 
			
		||||
@ -94,7 +94,7 @@ namespace Jellyfin.Server
 | 
			
		||||
 | 
			
		||||
            if (PublishedServerUrl != null)
 | 
			
		||||
            {
 | 
			
		||||
                config.Add(UdpServer.AddressOverrideConfigKey, PublishedServerUrl.ToString());
 | 
			
		||||
                config.Add(UdpServer.AddressOverrideConfigKey, PublishedServerUrl);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (FFmpegPath != null)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										28
									
								
								Jellyfin.sln
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								Jellyfin.sln
									
									
									
									
									
								
							@ -68,14 +68,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Implementat
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking", "Jellyfin.Networking\Jellyfin.Networking.csproj", "{0A3FCC4D-C714-4072-B90F-E374A15F9FF9}"
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\NetworkTesting\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}"
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "tests\Jellyfin.Dlna.Tests\Jellyfin.Dlna.Tests.csproj", "{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}"
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}"
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Model.Tests", "tests\Jellyfin.Model.Tests\Jellyfin.Model.Tests.csproj", "{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}"
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}"
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}"
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}"
 | 
			
		||||
EndProject
 | 
			
		||||
Global
 | 
			
		||||
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 | 
			
		||||
		Debug|Any CPU = Debug|Any CPU
 | 
			
		||||
@ -190,10 +194,6 @@ Global
 | 
			
		||||
		{0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
			
		||||
		{0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
			
		||||
		{0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
			
		||||
		{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 | 
			
		||||
		{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
			
		||||
		{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
			
		||||
		{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
			
		||||
		{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 | 
			
		||||
		{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
			
		||||
		{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
			
		||||
@ -206,6 +206,18 @@ Global
 | 
			
		||||
		{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
			
		||||
		{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
			
		||||
		{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
			
		||||
		{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 | 
			
		||||
		{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
			
		||||
		{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
			
		||||
		{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
			
		||||
		{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 | 
			
		||||
		{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
			
		||||
		{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
			
		||||
		{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
			
		||||
		{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 | 
			
		||||
		{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
			
		||||
		{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
			
		||||
		{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
			
		||||
	EndGlobalSection
 | 
			
		||||
	GlobalSection(SolutionProperties) = preSolution
 | 
			
		||||
		HideSolutionNode = FALSE
 | 
			
		||||
@ -217,10 +229,12 @@ Global
 | 
			
		||||
		{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
 | 
			
		||||
		{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
 | 
			
		||||
		{462584F7-5023-4019-9EAC-B98CA458C0A0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
 | 
			
		||||
		{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
 | 
			
		||||
		{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
 | 
			
		||||
		{30922383-D513-4F4D-B890-A940B57FA353} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
 | 
			
		||||
		{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
 | 
			
		||||
		{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
 | 
			
		||||
		{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
 | 
			
		||||
		{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
 | 
			
		||||
	EndGlobalSection
 | 
			
		||||
	GlobalSection(ExtensibilityGlobals) = postSolution
 | 
			
		||||
		SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										89
									
								
								MediaBrowser.Common/Crc32.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								MediaBrowser.Common/Crc32.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,89 @@
 | 
			
		||||
#pragma warning disable CS1591
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
 | 
			
		||||
namespace MediaBrowser.Common
 | 
			
		||||
{
 | 
			
		||||
    public static class Crc32
 | 
			
		||||
    {
 | 
			
		||||
        private static readonly uint[] _crcTable =
 | 
			
		||||
        {
 | 
			
		||||
            0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
 | 
			
		||||
            0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
 | 
			
		||||
            0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
 | 
			
		||||
            0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
 | 
			
		||||
            0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
 | 
			
		||||
            0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
 | 
			
		||||
            0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
 | 
			
		||||
            0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
 | 
			
		||||
            0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
 | 
			
		||||
            0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
 | 
			
		||||
            0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
 | 
			
		||||
            0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
 | 
			
		||||
            0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
 | 
			
		||||
            0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
 | 
			
		||||
            0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
 | 
			
		||||
            0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
 | 
			
		||||
            0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
 | 
			
		||||
            0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
 | 
			
		||||
            0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
 | 
			
		||||
            0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
 | 
			
		||||
            0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
 | 
			
		||||
            0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
 | 
			
		||||
            0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
 | 
			
		||||
            0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
 | 
			
		||||
            0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
 | 
			
		||||
            0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
 | 
			
		||||
            0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
 | 
			
		||||
            0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
 | 
			
		||||
            0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
 | 
			
		||||
            0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
 | 
			
		||||
            0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
 | 
			
		||||
            0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
 | 
			
		||||
            0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
 | 
			
		||||
            0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
 | 
			
		||||
            0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
 | 
			
		||||
            0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
 | 
			
		||||
            0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
 | 
			
		||||
            0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
 | 
			
		||||
            0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
 | 
			
		||||
            0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
 | 
			
		||||
            0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
 | 
			
		||||
            0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
 | 
			
		||||
            0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
 | 
			
		||||
            0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
 | 
			
		||||
            0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
 | 
			
		||||
            0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
 | 
			
		||||
            0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
 | 
			
		||||
            0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
 | 
			
		||||
            0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
 | 
			
		||||
            0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
 | 
			
		||||
            0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
 | 
			
		||||
            0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
 | 
			
		||||
            0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
 | 
			
		||||
            0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
 | 
			
		||||
            0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
 | 
			
		||||
            0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
 | 
			
		||||
            0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
 | 
			
		||||
            0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
 | 
			
		||||
            0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
 | 
			
		||||
            0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
 | 
			
		||||
            0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
 | 
			
		||||
            0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
 | 
			
		||||
            0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
 | 
			
		||||
            0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        public static uint Compute(ReadOnlySpan<byte> bytes)
 | 
			
		||||
        {
 | 
			
		||||
            var crc = 0xffffffff;
 | 
			
		||||
            var len = bytes.Length;
 | 
			
		||||
            for (var i = 0; i < len; i++)
 | 
			
		||||
            {
 | 
			
		||||
                crc = (crc >> 8) ^ _crcTable[(bytes[i] ^ crc) & 0xff];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return ~crc;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -10,7 +10,7 @@ namespace MediaBrowser.Common
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="type">Type to create.</param>
 | 
			
		||||
    /// <returns>New instance of type <param>type</param>.</returns>
 | 
			
		||||
    public delegate object CreationDelegate(Type type);
 | 
			
		||||
    public delegate object CreationDelegateFactory(Type type);
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// An interface to be implemented by the applications hosting a kernel.
 | 
			
		||||
@ -112,7 +112,7 @@ namespace MediaBrowser.Common
 | 
			
		||||
        /// <param name="defaultFunc">Delegate function that gets called to create the object.</param>
 | 
			
		||||
        /// <param name="manageLifetime">If set to <c>true</c> [manage lifetime].</param>
 | 
			
		||||
        /// <returns><see cref="IReadOnlyCollection{T}" />.</returns>
 | 
			
		||||
        IReadOnlyCollection<T> GetExports<T>(CreationDelegate defaultFunc, bool manageLifetime = true);
 | 
			
		||||
        IReadOnlyCollection<T> GetExports<T>(CreationDelegateFactory defaultFunc, bool manageLifetime = true);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the export types.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										23
									
								
								MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Text.Json;
 | 
			
		||||
using System.Text.Json.Serialization;
 | 
			
		||||
 | 
			
		||||
namespace MediaBrowser.Common.Json.Converters
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Converts a Version object or value to/from JSON.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <remarks>
 | 
			
		||||
    /// Required to send <see cref="Version"/> as a string instead of an object.
 | 
			
		||||
    /// </remarks>
 | 
			
		||||
    public class JsonVersionConverter : JsonConverter<Version>
 | 
			
		||||
    {
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        public override Version Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
 | 
			
		||||
            => new Version(reader.GetString());
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options)
 | 
			
		||||
            => writer.WriteStringValue(value.ToString());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -35,6 +35,7 @@ namespace MediaBrowser.Common.Json
 | 
			
		||||
            {
 | 
			
		||||
                new JsonGuidConverter(),
 | 
			
		||||
                new JsonNullableGuidConverter(),
 | 
			
		||||
                new JsonVersionConverter(),
 | 
			
		||||
                new JsonStringEnumConverter(),
 | 
			
		||||
                new JsonNullableStructConverterFactory(),
 | 
			
		||||
                new JsonBoolNumberConverter(),
 | 
			
		||||
@ -60,7 +61,7 @@ namespace MediaBrowser.Common.Json
 | 
			
		||||
        /// If the defaults must be modified the author must use the copy constructor.
 | 
			
		||||
        /// </remarks>
 | 
			
		||||
        /// <returns>The default <see cref="JsonSerializerOptions" /> options.</returns>
 | 
			
		||||
        public static JsonSerializerOptions GetOptions()
 | 
			
		||||
        public static JsonSerializerOptions Options
 | 
			
		||||
            => _jsonSerializerOptions;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
@ -71,7 +72,7 @@ namespace MediaBrowser.Common.Json
 | 
			
		||||
        /// If the defaults must be modified the author must use the copy constructor.
 | 
			
		||||
        /// </remarks>
 | 
			
		||||
        /// <returns>The camelCase <see cref="JsonSerializerOptions" /> options.</returns>
 | 
			
		||||
        public static JsonSerializerOptions GetCamelCaseOptions()
 | 
			
		||||
        public static JsonSerializerOptions CamelCaseOptions
 | 
			
		||||
            => _camelCaseJsonSerializerOptions;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
@ -82,7 +83,7 @@ namespace MediaBrowser.Common.Json
 | 
			
		||||
        /// If the defaults must be modified the author must use the copy constructor.
 | 
			
		||||
        /// </remarks>
 | 
			
		||||
        /// <returns>The PascalCase <see cref="JsonSerializerOptions" /> options.</returns>
 | 
			
		||||
        public static JsonSerializerOptions GetPascalCaseOptions()
 | 
			
		||||
        public static JsonSerializerOptions PascalCaseOptions
 | 
			
		||||
            => _pascalCaseJsonSerializerOptions;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -33,6 +33,8 @@
 | 
			
		||||
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
 | 
			
		||||
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
 | 
			
		||||
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
 | 
			
		||||
    <AnalysisMode>AllEnabledByDefault</AnalysisMode>
 | 
			
		||||
    <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
 | 
			
		||||
    <PublishRepositoryUrl>true</PublishRepositoryUrl>
 | 
			
		||||
    <EmbedUntrackedSources>true</EmbedUntrackedSources>
 | 
			
		||||
    <IncludeSymbols>true</IncludeSymbols>
 | 
			
		||||
@ -46,16 +48,11 @@
 | 
			
		||||
 | 
			
		||||
  <!-- Code analyzers-->
 | 
			
		||||
  <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
 | 
			
		||||
    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
 | 
			
		||||
    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
 | 
			
		||||
    <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
 | 
			
		||||
      <_Parameter1>Jellyfin.Common.Tests</_Parameter1>
 | 
			
		||||
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user