From a3a4689af22693b535e80b98624831866fda2a61 Mon Sep 17 00:00:00 2001 From: "Brian J. Murrell" Date: Thu, 22 Apr 2021 10:05:43 -0400 Subject: [PATCH 01/73] Allow to bind to priveleged ports (i.e. 80/443) Add "AmbientCapabilities=CAP_NET_BIND_SERVICE" to the "[Service]" section of the unit file to allow the server to bind to ports 80 and 443. Signed-off-by: Brian J. Murrell --- fedora/jellyfin.service | 1 + 1 file changed, 1 insertion(+) diff --git a/fedora/jellyfin.service b/fedora/jellyfin.service index b092ebf2f0..bde124a0fa 100644 --- a/fedora/jellyfin.service +++ b/fedora/jellyfin.service @@ -3,6 +3,7 @@ After=network.target Description=Jellyfin is a free software media system that puts you in control of managing and streaming your media. [Service] +AmbientCapabilities=CAP_NET_BIND_SERVICE EnvironmentFile=/etc/sysconfig/jellyfin WorkingDirectory=/var/lib/jellyfin ExecStart=/usr/bin/jellyfin ${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} From d5b63092ed1b4b6ef4da2a5cdccec472aa1c06b3 Mon Sep 17 00:00:00 2001 From: Orry Verducci Date: Thu, 24 Jun 2021 17:51:35 +0100 Subject: [PATCH 02/73] Add H.264 MBAFF interlace check Use the codec time base to determine if a MBAFF coded H.264 file is interlaced. --- .../Probing/ProbeResultNormalizer.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index bbff5dacac..dd3faf5766 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -686,11 +686,6 @@ namespace MediaBrowser.MediaEncoding.Probing stream.IsAVC = false; } - if (!string.IsNullOrWhiteSpace(streamInfo.FieldOrder) && !string.Equals(streamInfo.FieldOrder, "progressive", StringComparison.OrdinalIgnoreCase)) - { - stream.IsInterlaced = true; - } - // Filter out junk if (!string.IsNullOrWhiteSpace(streamInfo.CodecTagString) && !streamInfo.CodecTagString.Contains("[0]", StringComparison.OrdinalIgnoreCase)) { @@ -746,6 +741,19 @@ namespace MediaBrowser.MediaEncoding.Probing stream.AverageFrameRate = GetFrameRate(streamInfo.AverageFrameRate); stream.RealFrameRate = GetFrameRate(streamInfo.RFrameRate); + // Some interlaced H.264 files in mp4 containers using MBAFF coding aren't flagged as being interlaced by FFprobe, + // so for H.264 files we also check if the codec timebase duration is half the reported frame rate duration to + // determine if the file is interlaced + bool videoInterlaced = !string.IsNullOrWhiteSpace(streamInfo.FieldOrder) + && !string.Equals(streamInfo.FieldOrder, "progressive", StringComparison.OrdinalIgnoreCase); + bool h264MbaffCoded = string.Equals(stream.Codec, "h264", StringComparison.OrdinalIgnoreCase) + && string.IsNullOrWhiteSpace(streamInfo.FieldOrder) + && 1f / (stream.AverageFrameRate * 2) == GetFrameRate(stream.CodecTimeBase); + if (videoInterlaced || h264MbaffCoded) + { + stream.IsInterlaced = true; + } + if (isAudio || string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase) || string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase)) { From 9abe9e7e54cc454667ba2128b5d321631b5ece51 Mon Sep 17 00:00:00 2001 From: Orry Verducci Date: Sun, 31 Oct 2021 17:04:04 +0000 Subject: [PATCH 03/73] Add rounding to the time base check --- .../Probing/ProbeResultNormalizer.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index c9885ae76a..eb8850cd25 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -724,13 +724,17 @@ namespace MediaBrowser.MediaEncoding.Probing stream.RealFrameRate = GetFrameRate(streamInfo.RFrameRate); // Some interlaced H.264 files in mp4 containers using MBAFF coding aren't flagged as being interlaced by FFprobe, - // so for H.264 files we also check if the codec timebase duration is half the reported frame rate duration to - // determine if the file is interlaced + // so for H.264 files we also calculate the frame rate from the codec time base and check if it is double the reported + // frame rate (both rounded to the nearest integer) to determine if the file is interlaced + float roundedTimeBaseFPS = MathF.Round(1 / GetFrameRate(stream.CodecTimeBase) ?? 0); + float roundedDoubleFrameRate = MathF.Round(stream.AverageFrameRate * 2 ?? 0); + bool videoInterlaced = !string.IsNullOrWhiteSpace(streamInfo.FieldOrder) && !string.Equals(streamInfo.FieldOrder, "progressive", StringComparison.OrdinalIgnoreCase); bool h264MbaffCoded = string.Equals(stream.Codec, "h264", StringComparison.OrdinalIgnoreCase) && string.IsNullOrWhiteSpace(streamInfo.FieldOrder) - && 1f / (stream.AverageFrameRate * 2) == GetFrameRate(stream.CodecTimeBase); + && roundedTimeBaseFPS == roundedDoubleFrameRate; + if (videoInterlaced || h264MbaffCoded) { stream.IsInterlaced = true; From 69df004b9f62cb65986607d659fde0dcc74004ab Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 24 Nov 2021 13:00:12 +0100 Subject: [PATCH 04/73] Migrate network configuration safely --- .../ApplicationHost.cs | 18 -- Jellyfin.Api/Helpers/ClassMigrationHelper.cs | 71 ------ Jellyfin.Server/Migrations/MigrationRunner.cs | 63 ++++- .../CreateNetworkConfiguration.cs | 140 +++++++++++ Jellyfin.Server/Program.cs | 1 + .../Configuration/ServerConfiguration.cs | 222 ------------------ 6 files changed, 196 insertions(+), 319 deletions(-) delete mode 100644 Jellyfin.Api/Helpers/ClassMigrationHelper.cs create mode 100644 Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 903c311334..8892f7f40f 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -313,22 +313,6 @@ namespace Emby.Server.Implementations ? Environment.MachineName : ConfigurationManager.Configuration.ServerName; - /// - /// Temporary function to migration network settings out of system.xml and into network.xml. - /// TODO: remove at the point when a fixed migration path has been decided upon. - /// - private void MigrateNetworkConfiguration() - { - string path = Path.Combine(ConfigurationManager.CommonApplicationPaths.ConfigurationDirectoryPath, "network.xml"); - if (!File.Exists(path)) - { - var networkSettings = new NetworkConfiguration(); - ClassMigrationHelper.CopyProperties(ConfigurationManager.Configuration, networkSettings); - _xmlSerializer.SerializeToFile(networkSettings, path); - Logger.LogDebug("Successfully migrated network settings."); - } - } - public string ExpandVirtualPath(string path) { var appPaths = ApplicationPaths; @@ -513,8 +497,6 @@ namespace Emby.Server.Implementations ConfigurationManager.AddParts(GetExports()); - // Have to migrate settings here as migration subsystem not yet initialised. - MigrateNetworkConfiguration(); NetManager = new NetworkManager(ConfigurationManager, LoggerFactory.CreateLogger()); // Initialize runtime stat collection diff --git a/Jellyfin.Api/Helpers/ClassMigrationHelper.cs b/Jellyfin.Api/Helpers/ClassMigrationHelper.cs deleted file mode 100644 index 76fb27bcc3..0000000000 --- a/Jellyfin.Api/Helpers/ClassMigrationHelper.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Reflection; - -namespace Jellyfin.Api.Helpers -{ - /// - /// A static class for copying matching properties from one object to another. - /// TODO: remove at the point when a fixed migration path has been decided upon. - /// - public static class ClassMigrationHelper - { - /// - /// Extension for 'Object' that copies the properties to a destination object. - /// - /// The source. - /// The destination. - public static void CopyProperties(this object source, object destination) - { - // If any this null throw an exception. - if (source == null || destination == null) - { - throw new ArgumentException("Source or/and Destination Objects are null"); - } - - // Getting the Types of the objects. - Type typeDest = destination.GetType(); - Type typeSrc = source.GetType(); - - // Iterate the Properties of the source instance and populate them from their destination counterparts. - PropertyInfo[] srcProps = typeSrc.GetProperties(); - foreach (PropertyInfo srcProp in srcProps) - { - if (!srcProp.CanRead) - { - continue; - } - - var targetProperty = typeDest.GetProperty(srcProp.Name); - if (targetProperty == null) - { - continue; - } - - if (!targetProperty.CanWrite) - { - continue; - } - - var obj = targetProperty.GetSetMethod(true); - if (obj != null && obj.IsPrivate) - { - continue; - } - - var target = targetProperty.GetSetMethod(); - if (target != null && (target.Attributes & MethodAttributes.Static) != 0) - { - continue; - } - - if (!targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType)) - { - continue; - } - - // Passed all tests, lets set the value. - targetProperty.SetValue(destination, srcProp.GetValue(source, null), null); - } - } - } -} diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 7365c8dbc1..11b6c4912b 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -1,6 +1,11 @@ using System; +using System.Collections.Generic; +using System.IO; using System.Linq; +using Emby.Server.Implementations; +using Emby.Server.Implementations.Serialization; using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -11,6 +16,14 @@ namespace Jellyfin.Server.Migrations /// public sealed class MigrationRunner { + /// + /// The list of known pre-startup migrations, in order of applicability. + /// + private static readonly Type[] _preStartupMigrationTypes = + { + typeof(PreStartupRoutines.CreateNetworkConfiguration) + }; + /// /// The list of known migrations, in order of applicability. /// @@ -29,6 +42,7 @@ namespace Jellyfin.Server.Migrations typeof(Routines.MigrateAuthenticationDb) }; + /// /// Run all needed migrations. /// @@ -41,17 +55,50 @@ namespace Jellyfin.Server.Migrations .Select(m => ActivatorUtilities.CreateInstance(host.ServiceProvider, m)) .OfType() .ToArray(); - var migrationOptions = host.ConfigurationManager.GetConfiguration(MigrationsListStore.StoreKey); - if (!host.ConfigurationManager.Configuration.IsStartupWizardCompleted && migrationOptions.Applied.Count == 0) + var migrationOptions = host.ConfigurationManager.GetConfiguration(MigrationsListStore.StoreKey); + HandleStartupWizardCondition(migrations, migrationOptions, host.ConfigurationManager.Configuration.IsStartupWizardCompleted, logger); + PerformMigrations(migrations, migrationOptions, options => host.ConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, options), logger); + } + + /// + /// Run all needed pre-startup migrations. + /// + /// Application paths. + /// Factory for making the logger. + public static void RunPreStartup(ServerApplicationPaths appPaths, ILoggerFactory loggerFactory) + { + var logger = loggerFactory.CreateLogger(); + var migrations = _preStartupMigrationTypes + .Select(m => Activator.CreateInstance(m, appPaths, loggerFactory)) + .OfType() + .ToArray(); + + var xmlSerializer = new MyXmlSerializer(); + var migrationConfigPath = Path.Join(appPaths.ConfigurationDirectoryPath, MigrationsListStore.StoreKey.ToLowerInvariant() + ".xml"); + var migrationOptions = (MigrationOptions)xmlSerializer.DeserializeFromFile(typeof(MigrationOptions), migrationConfigPath)!; + + // We have to deserialize it manually since the configuration manager may overwrite it + var serverConfig = (ServerConfiguration)xmlSerializer.DeserializeFromFile(typeof(ServerConfiguration), appPaths.SystemConfigurationFilePath)!; + HandleStartupWizardCondition(migrations, migrationOptions, serverConfig.IsStartupWizardCompleted, logger); + PerformMigrations(migrations, migrationOptions, options => xmlSerializer.SerializeToFile(options, migrationConfigPath), logger); + } + + private static void HandleStartupWizardCondition(IEnumerable migrations, MigrationOptions migrationOptions, bool isStartWizardCompleted, ILogger logger) + { + if (isStartWizardCompleted || migrationOptions.Applied.Count != 0) { - // If startup wizard is not finished, this is a fresh install. - // Don't run any migrations, just mark all of them as applied. - logger.LogInformation("Marking all known migrations as applied because this is a fresh install"); - migrationOptions.Applied.AddRange(migrations.Where(m => !m.PerformOnNewInstall).Select(m => (m.Id, m.Name))); - host.ConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); + return; } + // If startup wizard is not finished, this is a fresh install. + var onlyOldInstalls = migrations.Where(m => !m.PerformOnNewInstall).ToArray(); + logger.LogInformation("Marking following migrations as applied because this is a fresh install: {@OnlyOldInstalls}", onlyOldInstalls.Select(m => m.Name)); + migrationOptions.Applied.AddRange(onlyOldInstalls.Select(m => (m.Id, m.Name))); + } + + private static void PerformMigrations(IMigrationRoutine[] migrations, MigrationOptions migrationOptions, Action saveConfiguration, ILogger logger) + { var appliedMigrationIds = migrationOptions.Applied.Select(m => m.Id).ToHashSet(); for (var i = 0; i < migrations.Length; i++) @@ -78,7 +125,7 @@ namespace Jellyfin.Server.Migrations // Mark the migration as completed logger.LogInformation("Migration '{Name}' applied successfully", migrationRoutine.Name); migrationOptions.Applied.Add((migrationRoutine.Id, migrationRoutine.Name)); - host.ConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); + saveConfiguration(migrationOptions); logger.LogDebug("Migration '{Name}' marked as applied in configuration.", migrationRoutine.Name); } } diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs new file mode 100644 index 0000000000..98c845dc3f --- /dev/null +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs @@ -0,0 +1,140 @@ +using System; +using System.IO; +using System.Runtime.Serialization; +using System.Xml; +using System.Xml.Serialization; +using Emby.Server.Implementations; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.PreStartupRoutines; + +/// +public class CreateNetworkConfiguration : IMigrationRoutine +{ + private readonly ServerApplicationPaths _applicationPaths; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// An instance of . + /// An instance of the interface. + public CreateNetworkConfiguration(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory) + { + _applicationPaths = applicationPaths; + _logger = loggerFactory.CreateLogger(); + } + + /// + public Guid Id => Guid.Parse("9B354818-94D5-4B68-AC49-E35CB85F9D84"); + + /// + public string Name => nameof(CreateNetworkConfiguration); + + /// + public bool PerformOnNewInstall => false; + + /// + public void Perform() + { + string path = Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "network.xml"); + if (File.Exists(path)) + { + _logger.LogDebug("Network configuration file already exists, skipping"); + return; + } + + var serverConfigSerializer = new XmlSerializer(typeof(OldNetworkConfiguration), new XmlRootAttribute("ServerConfiguration")); + using var xmlReader = XmlReader.Create(_applicationPaths.SystemConfigurationFilePath); + var networkSettings = serverConfigSerializer.Deserialize(xmlReader); + + var networkConfigSerializer = new XmlSerializer(typeof(OldNetworkConfiguration), new XmlRootAttribute("NetworkConfiguration")); + var xmlWriterSettings = new XmlWriterSettings { Indent = true }; + using var xmlWriter = XmlWriter.Create(path, xmlWriterSettings); + networkConfigSerializer.Serialize(xmlWriter, networkSettings); + } + +#pragma warning disable CS1591 + public sealed class OldNetworkConfiguration + { + public const int DefaultHttpPort = 8096; + + public const int DefaultHttpsPort = 8920; + + private string _baseUrl = string.Empty; + + public bool RequireHttps { get; set; } + + public string CertificatePath { get; set; } = string.Empty; + + public string CertificatePassword { get; set; } = string.Empty; + + public string BaseUrl + { + get => _baseUrl; + set + { + // Normalize the start of the string + if (string.IsNullOrWhiteSpace(value)) + { + // If baseUrl is empty, set an empty prefix string + _baseUrl = string.Empty; + return; + } + + if (value[0] != '/') + { + // If baseUrl was not configured with a leading slash, append one for consistency + value = "/" + value; + } + + // Normalize the end of the string + if (value[^1] == '/') + { + // If baseUrl was configured with a trailing slash, remove it for consistency + value = value.Remove(value.Length - 1); + } + + _baseUrl = value; + } + } + + public int PublicHttpsPort { get; set; } = DefaultHttpsPort; + + public int HttpServerPortNumber { get; set; } = DefaultHttpPort; + + public int HttpsPortNumber { get; set; } = DefaultHttpsPort; + + public bool EnableHttps { get; set; } + + public int PublicPort { get; set; } = DefaultHttpPort; + + public bool EnableIPV6 { get; set; } + + [DataMember(Name = "EnableIPV4")] + public bool EnableIPV4 { get; set; } = true; + + public bool IgnoreVirtualInterfaces { get; set; } = true; + + public string VirtualInterfaceNames { get; set; } = "vEthernet*"; + + public bool TrustAllIP6Interfaces { get; set; } + + public string[] PublishedServerUriBySubnet { get; set; } = Array.Empty(); + + public string[] RemoteIPFilter { get; set; } = Array.Empty(); + + public bool IsRemoteIPFilterBlacklist { get; set; } + + public bool EnableUPnP { get; set; } + + public bool EnableRemoteAccess { get; set; } = true; + + public string[] LocalNetworkSubnets { get; set; } = Array.Empty(); + + public string[] LocalNetworkAddresses { get; set; } = Array.Empty(); + + public string[] KnownProxies { get; set; } = Array.Empty(); + } +#pragma warning restore CS1591 +} diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 7f158aebb4..f40526e223 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -175,6 +175,7 @@ namespace Jellyfin.Server } PerformStaticInitialization(); + Migrations.MigrationRunner.RunPreStartup(appPaths, _loggerFactory); var appHost = new CoreAppHost( appPaths, diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 0ab721b77d..46e61ee1a2 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -13,18 +13,6 @@ namespace MediaBrowser.Model.Configuration /// public class ServerConfiguration : BaseApplicationConfiguration { - /// - /// The default value for . - /// - public const int DefaultHttpPort = 8096; - - /// - /// The default value for and . - /// - public const int DefaultHttpsPort = 8920; - - private string _baseUrl = string.Empty; - /// /// Initializes a new instance of the class. /// @@ -75,149 +63,13 @@ namespace MediaBrowser.Model.Configuration }; } - /// - /// Gets or sets a value indicating whether to enable automatic port forwarding. - /// - public bool EnableUPnP { get; set; } = false; - /// /// Gets or sets a value indicating whether to enable prometheus metrics exporting. /// public bool EnableMetrics { get; set; } = false; - /// - /// Gets or sets the public mapped port. - /// - /// The public mapped port. - public int PublicPort { get; set; } = DefaultHttpPort; - - /// - /// Gets or sets a value indicating whether the http port should be mapped as part of UPnP automatic port forwarding. - /// - public bool UPnPCreateHttpPortMap { get; set; } = false; - - /// - /// Gets or sets client udp port range. - /// - public string UDPPortRange { get; set; } = string.Empty; - - /// - /// Gets or sets a value indicating whether IPV6 capability is enabled. - /// - public bool EnableIPV6 { get; set; } = false; - - /// - /// Gets or sets a value indicating whether IPV4 capability is enabled. - /// - public bool EnableIPV4 { get; set; } = true; - - /// - /// Gets or sets a value indicating whether detailed ssdp logs are sent to the console/log. - /// "Emby.Dlna": "Debug" must be set in logging.default.json for this property to work. - /// - public bool EnableSSDPTracing { get; set; } = false; - - /// - /// Gets or sets a value indicating whether an IP address is to be used to filter the detailed ssdp logs that are being sent to the console/log. - /// If the setting "Emby.Dlna": "Debug" msut be set in logging.default.json for this property to work. - /// - public string SSDPTracingFilter { get; set; } = string.Empty; - - /// - /// Gets or sets the number of times SSDP UDP messages are sent. - /// - public int UDPSendCount { get; set; } = 2; - - /// - /// Gets or sets the delay between each groups of SSDP messages (in ms). - /// - public int UDPSendDelay { get; set; } = 100; - - /// - /// Gets or sets a value indicating whether address names that match should be Ignore for the purposes of binding. - /// - public bool IgnoreVirtualInterfaces { get; set; } = true; - - /// - /// Gets or sets a value indicating the interfaces that should be ignored. The list can be comma separated. . - /// - public string VirtualInterfaceNames { get; set; } = "vEthernet*"; - - /// - /// Gets or sets the time (in seconds) between the pings of SSDP gateway monitor. - /// - public int GatewayMonitorPeriod { get; set; } = 60; - - /// - /// Gets a value indicating whether multi-socket binding is available. - /// - public bool EnableMultiSocketBinding { get; } = true; - - /// - /// Gets or sets a value indicating whether all IPv6 interfaces should be treated as on the internal network. - /// Depending on the address range implemented ULA ranges might not be used. - /// - public bool TrustAllIP6Interfaces { get; set; } = false; - - /// - /// Gets or sets the ports that HDHomerun uses. - /// - public string HDHomerunPortRange { get; set; } = string.Empty; - - /// - /// Gets or sets PublishedServerUri to advertise for specific subnets. - /// - public string[] PublishedServerUriBySubnet { get; set; } = Array.Empty(); - - /// - /// Gets or sets a value indicating whether Autodiscovery tracing is enabled. - /// - public bool AutoDiscoveryTracing { get; set; } = false; - - /// - /// Gets or sets a value indicating whether Autodiscovery is enabled. - /// - public bool AutoDiscovery { get; set; } = true; - - /// - /// Gets or sets the public HTTPS port. - /// - /// The public HTTPS port. - public int PublicHttpsPort { get; set; } = DefaultHttpsPort; - - /// - /// Gets or sets the HTTP server port number. - /// - /// The HTTP server port number. - public int HttpServerPortNumber { get; set; } = DefaultHttpPort; - - /// - /// Gets or sets the HTTPS server port number. - /// - /// The HTTPS server port number. - public int HttpsPortNumber { get; set; } = DefaultHttpsPort; - - /// - /// Gets or sets a value indicating whether to use HTTPS. - /// - /// - /// In order for HTTPS to be used, in addition to setting this to true, valid values must also be - /// provided for and . - /// - public bool EnableHttps { get; set; } = false; - public bool EnableNormalizedItemByNameIds { get; set; } = true; - /// - /// Gets or sets the filesystem path of an X.509 certificate to use for SSL. - /// - public string CertificatePath { get; set; } = string.Empty; - - /// - /// Gets or sets the password required to access the X.509 certificate data in the file specified by . - /// - public string CertificatePassword { get; set; } = string.Empty; - /// /// Gets or sets a value indicating whether this instance is port authorized. /// @@ -229,11 +81,6 @@ namespace MediaBrowser.Model.Configuration /// public bool QuickConnectAvailable { get; set; } = false; - /// - /// Gets or sets a value indicating whether access outside of the LAN is permitted. - /// - public bool EnableRemoteAccess { get; set; } = true; - /// /// Gets or sets a value indicating whether [enable case sensitive item ids]. /// @@ -318,13 +165,6 @@ namespace MediaBrowser.Model.Configuration /// The file watcher delay. public int LibraryMonitorDelay { get; set; } = 60; - /// - /// Gets or sets a value indicating whether [enable dashboard response caching]. - /// Allows potential contributors without visual studio to modify production dashboard code and test changes. - /// - /// true if [enable dashboard response caching]; otherwise, false. - public bool EnableDashboardResponseCaching { get; set; } = true; - /// /// Gets or sets the image saving convention. /// @@ -337,36 +177,6 @@ namespace MediaBrowser.Model.Configuration public string ServerName { get; set; } = string.Empty; - public string BaseUrl - { - get => _baseUrl; - set - { - // Normalize the start of the string - if (string.IsNullOrWhiteSpace(value)) - { - // If baseUrl is empty, set an empty prefix string - _baseUrl = string.Empty; - return; - } - - if (value[0] != '/') - { - // If baseUrl was not configured with a leading slash, append one for consistency - value = "/" + value; - } - - // Normalize the end of the string - if (value[value.Length - 1] == '/') - { - // If baseUrl was configured with a trailing slash, remove it for consistency - value = value.Remove(value.Length - 1); - } - - _baseUrl = value; - } - } - public string UICulture { get; set; } = "en-US"; public bool SaveMetadataHidden { get; set; } = false; @@ -381,43 +191,16 @@ namespace MediaBrowser.Model.Configuration public bool DisplaySpecialsWithinSeasons { get; set; } = true; - /// - /// Gets or sets the subnets that are deemed to make up the LAN. - /// - public string[] LocalNetworkSubnets { get; set; } = Array.Empty(); - - /// - /// Gets or sets the interface addresses which Jellyfin will bind to. If empty, all interfaces will be used. - /// - public string[] LocalNetworkAddresses { get; set; } = Array.Empty(); - public string[] CodecsUsed { get; set; } = Array.Empty(); public List PluginRepositories { get; set; } = new List(); public bool EnableExternalContentInSuggestions { get; set; } = true; - /// - /// Gets or sets a value indicating whether the server should force connections over HTTPS. - /// - public bool RequireHttps { get; set; } = false; - - /// - /// Gets or sets the filter for remote IP connectivity. Used in conjuntion with . - /// - public string[] RemoteIPFilter { get; set; } = Array.Empty(); - - /// - /// Gets or sets a value indicating whether contains a blacklist or a whitelist. Default is a whitelist. - /// - public bool IsRemoteIPFilterBlacklist { get; set; } = false; - public int ImageExtractionTimeoutMs { get; set; } = 0; public PathSubstitution[] PathSubstitutions { get; set; } = Array.Empty(); - public string[] UninstalledPlugins { get; set; } = Array.Empty(); - /// /// Gets or sets a value indicating whether slow server responses should be logged as a warning. /// @@ -433,11 +216,6 @@ namespace MediaBrowser.Model.Configuration /// public string[] CorsHosts { get; set; } = new[] { "*" }; - /// - /// Gets or sets the known proxies. - /// - public string[] KnownProxies { get; set; } = Array.Empty(); - /// /// Gets or sets the number of days we should retain activity logs. /// From 0485ff189948e4c0b6532e835b9f6740e89d0d40 Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 24 Nov 2021 13:42:14 +0100 Subject: [PATCH 05/73] Create a store key constant for network --- Emby.Dlna/Main/DlnaEntryPoint.cs | 2 +- Jellyfin.Api/Controllers/StartupController.cs | 2 +- .../NetworkConfigurationFactory.cs | 6 +---- .../NetworkConfigurationStore.cs | 24 +++++++++++++++++++ Jellyfin.Networking/Manager/NetworkManager.cs | 2 +- 5 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 Jellyfin.Networking/Configuration/NetworkConfigurationStore.cs diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index f35d90f21c..08f639d932 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -124,7 +124,7 @@ namespace Emby.Dlna.Main config); Current = this; - var netConfig = config.GetConfiguration("network"); + var netConfig = config.GetConfiguration(NetworkConfigurationStore.StoreKey); _disabled = appHost.ListenWithHttps && netConfig.RequireHttps; if (_disabled && _config.GetDlnaConfiguration().EnableServer) diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index a01a617fc0..c49bde93f1 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -93,7 +93,7 @@ namespace Jellyfin.Api.Controllers NetworkConfiguration settings = _config.GetNetworkConfiguration(); settings.EnableRemoteAccess = startupRemoteAccessDto.EnableRemoteAccess; settings.EnableUPnP = startupRemoteAccessDto.EnableAutomaticPortMapping; - _config.SaveConfiguration("network", settings); + _config.SaveConfiguration(NetworkConfigurationStore.StoreKey, settings); return NoContent(); } diff --git a/Jellyfin.Networking/Configuration/NetworkConfigurationFactory.cs b/Jellyfin.Networking/Configuration/NetworkConfigurationFactory.cs index ac0485d871..14726565aa 100644 --- a/Jellyfin.Networking/Configuration/NetworkConfigurationFactory.cs +++ b/Jellyfin.Networking/Configuration/NetworkConfigurationFactory.cs @@ -16,11 +16,7 @@ namespace Jellyfin.Networking.Configuration { return new[] { - new ConfigurationStore - { - Key = "network", - ConfigurationType = typeof(NetworkConfiguration) - } + new NetworkConfigurationStore() }; } } diff --git a/Jellyfin.Networking/Configuration/NetworkConfigurationStore.cs b/Jellyfin.Networking/Configuration/NetworkConfigurationStore.cs new file mode 100644 index 0000000000..a268ebb68f --- /dev/null +++ b/Jellyfin.Networking/Configuration/NetworkConfigurationStore.cs @@ -0,0 +1,24 @@ +using MediaBrowser.Common.Configuration; + +namespace Jellyfin.Networking.Configuration +{ + /// + /// A configuration that stores network related settings. + /// + public class NetworkConfigurationStore : ConfigurationStore + { + /// + /// The name of the configuration in the storage. + /// + public const string StoreKey = "network"; + + /// + /// Initializes a new instance of the class. + /// + public NetworkConfigurationStore() + { + ConfigurationType = typeof(NetworkConfiguration); + Key = StoreKey; + } + } +} diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index cf002dc738..58b30ad2d2 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -727,7 +727,7 @@ namespace Jellyfin.Networking.Manager private void ConfigurationUpdated(object? sender, ConfigurationUpdateEventArgs evt) { - if (evt.Key.Equals("network", StringComparison.Ordinal)) + if (evt.Key.Equals(NetworkConfigurationStore.StoreKey, StringComparison.Ordinal)) { UpdateSettings((NetworkConfiguration)evt.NewConfiguration); } From 0b871505a6d8caa124d3578ce915f6b4162a8ef2 Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 24 Nov 2021 14:36:01 +0100 Subject: [PATCH 06/73] Remove stray datamember --- .../Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs index 98c845dc3f..a951f751e3 100644 --- a/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Runtime.Serialization; using System.Xml; using System.Xml.Serialization; using Emby.Server.Implementations; @@ -111,7 +110,6 @@ public class CreateNetworkConfiguration : IMigrationRoutine public bool EnableIPV6 { get; set; } - [DataMember(Name = "EnableIPV4")] public bool EnableIPV4 { get; set; } = true; public bool IgnoreVirtualInterfaces { get; set; } = true; From 4890454935f6c56ff4d2fab0b71646311319c0ac Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sat, 27 Nov 2021 07:28:58 -0700 Subject: [PATCH 07/73] Add additional provider parsing to series file name --- .../Library/Resolvers/TV/SeriesResolver.cs | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 4e15acd182..64272fde1a 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -187,11 +187,40 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV { var justName = Path.GetFileName(path); - var id = justName.GetAttributeValue("tvdbid"); - - if (!string.IsNullOrEmpty(id)) + var tvdbId = justName.GetAttributeValue("tvdbid"); + if (!string.IsNullOrEmpty(tvdbId)) { - item.SetProviderId(MetadataProvider.Tvdb, id); + item.SetProviderId(MetadataProvider.Tvdb, tvdbId); + } + + var tvmazeId = justName.GetAttributeValue("tvmazeid"); + if (!string.IsNullOrEmpty(tvmazeId)) + { + item.SetProviderId(MetadataProvider.TvMaze, tvmazeId); + } + + var tmdbId = justName.GetAttributeValue("tmdbid"); + if (!string.IsNullOrEmpty(tmdbId)) + { + item.SetProviderId(MetadataProvider.Tmdb, tmdbId); + } + + var anidbId = justName.GetAttributeValue("anidbid"); + if (!string.IsNullOrEmpty(anidbId)) + { + item.SetProviderId("AniDB", anidbId); + } + + var aniListId = justName.GetAttributeValue("anilistid"); + if (!string.IsNullOrEmpty(aniListId)) + { + item.SetProviderId("AniList", aniListId); + } + + var aniSearchId = justName.GetAttributeValue("anisearchid"); + if (!string.IsNullOrEmpty(aniSearchId)) + { + item.SetProviderId("AniSearch", aniSearchId); } } } From a87b87825dac31f3739a8dc0254548c3e81a241e Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sat, 27 Nov 2021 16:01:27 +0100 Subject: [PATCH 08/73] Update Jellyfin.Server/Migrations/MigrationRunner.cs Co-authored-by: Cody Robibero --- Jellyfin.Server/Migrations/MigrationRunner.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 11b6c4912b..a6886c64a6 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -42,7 +42,6 @@ namespace Jellyfin.Server.Migrations typeof(Routines.MigrateAuthenticationDb) }; - /// /// Run all needed migrations. /// From 065d3fa837bf5d66d4b4e5382257b4ce76b9d0b3 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sat, 27 Nov 2021 16:10:43 -0700 Subject: [PATCH 09/73] performance++ --- .../Library/PathExtensions.cs | 23 ++++++++++++------- .../Resolvers/Movies/BoxSetResolver.cs | 2 +- .../Library/Resolvers/Movies/MovieResolver.cs | 6 ++--- .../Library/Resolvers/TV/SeriesResolver.cs | 2 +- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index d5b855cdf2..8ce054c38c 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library /// The attrib. /// System.String. /// or is empty. - public static string? GetAttributeValue(this string str, string attribute) + public static string? GetAttributeValue(this ReadOnlySpan str, ReadOnlySpan attribute) { if (str.Length == 0) { @@ -28,17 +28,24 @@ namespace Emby.Server.Implementations.Library throw new ArgumentException("String can't be empty.", nameof(attribute)); } - string srch = "[" + attribute + "="; - int start = str.IndexOf(srch, StringComparison.OrdinalIgnoreCase); - if (start != -1) + var openBracketIndex = str.IndexOf('['); + var equalsIndex = str.IndexOf('='); + var closingBracketIndex = str.IndexOf(']'); + while (openBracketIndex < equalsIndex && equalsIndex < closingBracketIndex) { - start += srch.Length; - int end = str.IndexOf(']', start); - return str.Substring(start, end - start); + if (str[(openBracketIndex + 1)..equalsIndex].Equals(attribute, StringComparison.OrdinalIgnoreCase)) + { + return str[(equalsIndex + 1)..closingBracketIndex].Trim().ToString(); + } + + str = str[(closingBracketIndex+ 1)..]; + openBracketIndex = str.IndexOf('['); + equalsIndex = str.IndexOf('='); + closingBracketIndex = str.IndexOf(']'); } // for imdbid we also accept pattern matching - if (string.Equals(attribute, "imdbid", StringComparison.OrdinalIgnoreCase)) + if (attribute.Equals("imdbid", StringComparison.OrdinalIgnoreCase)) { var match = ProviderIdParsers.TryFindImdbId(str, out var imdbId); return match ? imdbId.ToString() : null; diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs index e7abe1e6da..6cc04ea810 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs @@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies private static void SetProviderIdFromPath(BaseItem item) { // we need to only look at the name of this actual item (not parents) - var justName = Path.GetFileName(item.Path); + var justName = Path.GetFileName(item.Path.AsSpan()); var id = justName.GetAttributeValue("tmdbid"); diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 732be0fe5c..ddd3fae882 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -342,9 +342,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies if (item is Movie || item is MusicVideo) { // We need to only look at the name of this actual item (not parents) - var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.ContainingFolderPath); + var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path.AsSpan()) : Path.GetFileName(item.ContainingFolderPath.AsSpan()); - if (!string.IsNullOrEmpty(justName)) + if (!justName.IsEmpty) { // check for tmdb id var tmdbid = justName.GetAttributeValue("tmdbid"); @@ -358,7 +358,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies if (!string.IsNullOrEmpty(item.Path)) { // check for imdb id - we use full media path, as we can assume, that this will match in any use case (wither id in parent dir or in file name) - var imdbid = item.Path.GetAttributeValue("imdbid"); + var imdbid = item.Path.AsSpan().GetAttributeValue("imdbid"); if (!string.IsNullOrWhiteSpace(imdbid)) { diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 64272fde1a..46e36847dc 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -185,7 +185,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV /// The path. private static void SetProviderIdFromPath(Series item, string path) { - var justName = Path.GetFileName(path); + var justName = Path.GetFileName(path.AsSpan()); var tvdbId = justName.GetAttributeValue("tvdbid"); if (!string.IsNullOrEmpty(tvdbId)) From 1df56335eec0430bef0d4dface75f69691134c10 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sat, 27 Nov 2021 16:25:21 -0700 Subject: [PATCH 10/73] Add more tests --- .../Library/PathExtensionsTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs index c5cc056f5d..d6cbe96474 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs @@ -11,6 +11,9 @@ namespace Jellyfin.Server.Implementations.Tests.Library [InlineData("Superman: Red Son - tt10985510", "imdbid", "tt10985510")] [InlineData("Superman: Red Son", "imdbid", null)] [InlineData("Superman: Red Son", "something", null)] + [InlineData("Superman: Red Son [imdbid1=tt11111111][imdbid=tt10985510]", "imdbid", "tt10985510")] + [InlineData("Superman: Red Son [tmdbid=618355][imdbid=tt10985510]", "imdbid", "tt10985510")] + [InlineData("Superman: Red Son [tmdbid=618355][imdbid=tt10985510]", "tmdbid", "618355")] public void GetAttributeValue_ValidArgs_Correct(string input, string attribute, string? expectedResult) { Assert.Equal(expectedResult, PathExtensions.GetAttributeValue(input, attribute)); From 148fcb1bb82d734136a5d19664276b47e71f2aa1 Mon Sep 17 00:00:00 2001 From: "Brian J. Murrell" Date: Tue, 30 Nov 2021 01:18:27 -0500 Subject: [PATCH 11/73] Put low port privilege into an optional subpackage Move "AmbientCapabilities=CAP_NET_BIND_SERVICE" to the "[Service]" section of the optional "lowport" unit drop-in file and package that drop-in in a separate, optionally installable jellyfin-server-lowports subpackage. This isolates the CAP_NET_BIND_SERVICE capability to only installations that desire it. Signed-off-by: Brian J. Murrell --- fedora/jellyfin-server-lowports.conf | 4 ++++ fedora/jellyfin.service | 1 - fedora/jellyfin.spec | 20 +++++++++++++++++++- 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 fedora/jellyfin-server-lowports.conf diff --git a/fedora/jellyfin-server-lowports.conf b/fedora/jellyfin-server-lowports.conf new file mode 100644 index 0000000000..eeb48a4e4b --- /dev/null +++ b/fedora/jellyfin-server-lowports.conf @@ -0,0 +1,4 @@ +# This allows Jellyfin to bind to low ports such as 80 and/or 443 + +[Service] +AmbientCapabilities=CAP_NET_BIND_SERVICE \ No newline at end of file diff --git a/fedora/jellyfin.service b/fedora/jellyfin.service index b91e1a566e..f706b0ad3f 100644 --- a/fedora/jellyfin.service +++ b/fedora/jellyfin.service @@ -3,7 +3,6 @@ After=network-online.target Description=Jellyfin is a free software media system that puts you in control of managing and streaming your media. [Service] -AmbientCapabilities=CAP_NET_BIND_SERVICE EnvironmentFile=/etc/sysconfig/jellyfin WorkingDirectory=/var/lib/jellyfin ExecStart=/usr/bin/jellyfin ${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} diff --git a/fedora/jellyfin.spec b/fedora/jellyfin.spec index 47dee7c13f..a4584364ef 100644 --- a/fedora/jellyfin.spec +++ b/fedora/jellyfin.spec @@ -12,7 +12,7 @@ Release: 1%{?dist} Summary: The Free Software Media System License: GPLv3 URL: https://jellyfin.org -# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%{version}.tar.gz` +# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%%{version}.tar.gz` Source0: jellyfin-server-%{version}.tar.gz Source11: jellyfin.service Source12: jellyfin.env @@ -20,6 +20,7 @@ Source13: jellyfin.sudoers Source14: restart.sh Source15: jellyfin.override.conf Source16: jellyfin-firewalld.xml +Source17: jellyfin-server-lowports.conf %{?systemd_requires} BuildRequires: systemd @@ -45,6 +46,16 @@ Requires: libcurl, fontconfig, freetype, openssl, glibc, libicu, at, sudo %description server The Jellyfin media server backend. +%package server-lowports +# RPMfusion free +Summary: The Free Software Media System Server backend. Low-port binding. +Requires: jellyfin-server + +%description server-lowports +The Jellyfin media server backend low port binding package. This package +enables binding to ports < 1024. You would install this if you want +the Jellyfin server to bind to ports 80 and/or 443 for example. + %prep %autosetup -n jellyfin-server-%{version} -b 0 @@ -57,6 +68,7 @@ dotnet publish --configuration Release --output='%{buildroot}%{_libdir}/jellyfin "-p:DebugSymbols=false;DebugType=none" Jellyfin.Server %{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE %{__install} -D -m 0644 %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf +%{__install} -D -m 0644 %{SOURCE17} %{buildroot}%{_unitdir}/jellyfin.service.d/jellyfin-server-lowports.conf %{__install} -D -m 0644 Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/jellyfin/logging.json %{__mkdir} -p %{buildroot}%{_bindir} tee %{buildroot}%{_bindir}/jellyfin << EOF @@ -95,6 +107,9 @@ EOF %attr(750,jellyfin,jellyfin) %dir %{_var}/cache/jellyfin %{_datadir}/licenses/jellyfin/LICENSE +%files server-lowports +%{_unitdir}/jellyfin.service.d/jellyfin-server-lowports.conf + %pre server getent group jellyfin >/dev/null || groupadd -r jellyfin getent passwd jellyfin >/dev/null || \ @@ -137,6 +152,9 @@ fi %systemd_postun_with_restart jellyfin.service %changelog +* Mon Nov 29 2021 Brian J. Murrell +- Add jellyfin-server-lowports.service drop-in in a server-lowports + subpackage to allow binding to low ports * Fri Dec 04 2020 Jellyfin Packaging Team - Forthcoming stable release * Mon Jul 27 2020 Jellyfin Packaging Team From 997816443862a8db68f314a6d23ef076c9661c01 Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Mon, 22 Nov 2021 21:47:52 +0100 Subject: [PATCH 12/73] Add support for external audio files --- MediaBrowser.Controller/Entities/Video.cs | 6 + .../MediaEncoding/EncodingHelper.cs | 21 +- .../MediaInfo/AudioResolver.cs | 275 ++++++++++++++++++ .../MediaInfo/FFProbeProvider.cs | 11 + .../MediaInfo/FFProbeVideoInfo.cs | 25 ++ 5 files changed, 334 insertions(+), 4 deletions(-) create mode 100644 MediaBrowser.Providers/MediaInfo/AudioResolver.cs diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index de42c67d38..56e955adad 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -97,6 +97,12 @@ namespace MediaBrowser.Controller.Entities /// The subtitle paths. public string[] SubtitleFiles { get; set; } + /// + /// Gets or sets the audio paths. + /// + /// The audio paths. + public string[] AudioFiles { get; set; } + /// /// Gets or sets a value indicating whether this instance has subtitles. /// diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5715194b85..5326ecd20c 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -678,6 +678,12 @@ namespace MediaBrowser.Controller.MediaEncoding arg.Append("-i ") .Append(GetInputPathArgument(state)); + if (state.AudioStream.IsExternal) + { + arg.Append(" -i ") + .Append(string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", state.AudioStream.Path)); + } + if (state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) @@ -1999,10 +2005,17 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.AudioStream != null) { - args += string.Format( - CultureInfo.InvariantCulture, - " -map 0:{0}", - state.AudioStream.Index); + if (state.AudioStream.IsExternal) + { + args += " -map 1:a"; + } + else + { + args += string.Format( + CultureInfo.InvariantCulture, + " -map 0:{0}", + state.AudioStream.Index); + } } else { diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs new file mode 100644 index 0000000000..fce2fa5512 --- /dev/null +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -0,0 +1,275 @@ +#nullable disable + +#pragma warning disable CA1002, CS1591 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.MediaInfo; + +namespace MediaBrowser.Providers.MediaInfo +{ + public class AudioResolver + { + private readonly ILocalizationManager _localization; + + private readonly IMediaEncoder _mediaEncoder; + + private readonly CancellationToken _cancellationToken; + + public AudioResolver(ILocalizationManager localization, IMediaEncoder mediaEncoder, CancellationToken cancellationToken = default) + { + _localization = localization; + _mediaEncoder = mediaEncoder; + _cancellationToken = cancellationToken; + } + + public List GetExternalAudioStreams( + Video video, + int startIndex, + IDirectoryService directoryService, + bool clearCache) + { + var streams = new List(); + + if (!video.IsFileProtocol) + { + return streams; + } + + AddExternalAudioStreams(streams, video.ContainingFolderPath, video.Path, startIndex, directoryService, clearCache); + + startIndex += streams.Count; + + string folder = video.GetInternalMetadataPath(); + + if (!Directory.Exists(folder)) + { + return streams; + } + + try + { + AddExternalAudioStreams(streams, folder, video.Path, startIndex, directoryService, clearCache); + } + catch (IOException) + { + } + + return streams; + } + + public IEnumerable GetExternalAudioFiles( + Video video, + IDirectoryService directoryService, + bool clearCache) + { + if (!video.IsFileProtocol) + { + yield break; + } + + var streams = GetExternalAudioStreams(video, 0, directoryService, clearCache); + + foreach (var stream in streams) + { + yield return stream.Path; + } + } + + public void AddExternalAudioStreams( + List streams, + string videoPath, + int startIndex, + IReadOnlyList files) + { + var videoFileNameWithoutExtension = NormalizeFilenameForAudioComparison(videoPath); + + for (var i = 0; i < files.Count; i++) + { + + var fullName = files[i]; + var extension = Path.GetExtension(fullName.AsSpan()); + if (!IsAudioExtension(extension)) + { + continue; + } + + Model.MediaInfo.MediaInfo mediaInfo = GetMediaInfo(fullName).Result; + MediaStream mediaStream = mediaInfo.MediaStreams.First(); + mediaStream.Index = startIndex++; + mediaStream.Type = MediaStreamType.Audio; + mediaStream.IsExternal = true; + mediaStream.Path = fullName; + mediaStream.IsDefault = false; + mediaStream.Title = null; + + var fileNameWithoutExtension = NormalizeFilenameForAudioComparison(fullName); + + // The audio filename must either be equal to the video filename or start with the video filename followed by a dot + if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + { + mediaStream.Path = fullName; + } + else if (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length + && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' + && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + { + + // Support xbmc naming conventions - 300.spanish.m4a + var languageSpan = fileNameWithoutExtension; + while (languageSpan.Length > 0) + { + var lastDot = languageSpan.LastIndexOf('.'); + var currentSlice = languageSpan[lastDot..]; + languageSpan = languageSpan[(lastDot + 1)..]; + break; + } + + // Try to translate to three character code + // Be flexible and check against both the full and three character versions + var language = languageSpan.ToString(); + var culture = _localization.FindLanguageInfo(language); + + language = culture == null ? language : culture.ThreeLetterISOLanguageName; + mediaStream.Language = language; + } + else + { + continue; + } + + mediaStream.Codec = extension.TrimStart('.').ToString().ToLowerInvariant(); + + streams.Add(mediaStream); + } + } + + private static bool IsAudioExtension(ReadOnlySpan extension) + { + String[] audioExtensions = new[] + { + ".nsv", + ".m4a", + ".flac", + ".aac", + ".strm", + ".pls", + ".rm", + ".mpa", + ".wav", + ".wma", + ".ogg", + ".opus", + ".mp3", + ".mp2", + ".mod", + ".amf", + ".669", + ".dmf", + ".dsm", + ".far", + ".gdm", + ".imf", + ".it", + ".m15", + ".med", + ".okt", + ".s3m", + ".stm", + ".sfx", + ".ult", + ".uni", + ".xm", + ".sid", + ".ac3", + ".dts", + ".cue", + ".aif", + ".aiff", + ".ape", + ".mac", + ".mpc", + ".mp+", + ".mpp", + ".shn", + ".wv", + ".nsf", + ".spc", + ".gym", + ".adplug", + ".adx", + ".dsp", + ".adp", + ".ymf", + ".ast", + ".afc", + ".hps", + ".xsp", + ".acc", + ".m4b", + ".oga", + ".dsf", + ".mka" + }; + + foreach (String audioExtension in audioExtensions) + { + if (extension.Equals(audioExtension, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + + private Task GetMediaInfo(string path) + { + _cancellationToken.ThrowIfCancellationRequested(); + + return _mediaEncoder.GetMediaInfo( + new MediaInfoRequest + { + MediaType = DlnaProfileType.Audio, + MediaSource = new MediaSourceInfo + { + Path = path, + Protocol = MediaProtocol.File + } + }, + _cancellationToken); + } + + private static ReadOnlySpan NormalizeFilenameForAudioComparison(string filename) + { + // Try to account for sloppy file naming + filename = filename.Replace("_", string.Empty, StringComparison.Ordinal); + filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal); + return Path.GetFileNameWithoutExtension(filename.AsSpan()); + } + + private void AddExternalAudioStreams( + List streams, + string folder, + string videoPath, + int startIndex, + IDirectoryService directoryService, + bool clearCache) + { + var files = directoryService.GetFilePaths(folder, clearCache, true); + + AddExternalAudioStreams(streams, videoPath, startIndex, files); + } + } +} diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index d4b5d8655c..1e7fcf2d20 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -50,6 +50,8 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IMediaSourceManager _mediaSourceManager; private readonly SubtitleResolver _subtitleResolver; + private readonly AudioResolver _audioResolver; + private readonly Task _cachedTask = Task.FromResult(ItemUpdateType.None); public FFProbeProvider( @@ -78,6 +80,7 @@ namespace MediaBrowser.Providers.MediaInfo _mediaSourceManager = mediaSourceManager; _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager); + _audioResolver = new AudioResolver(BaseItem.LocalizationManager, mediaEncoder); } public string Name => "ffprobe"; @@ -111,6 +114,14 @@ namespace MediaBrowser.Providers.MediaInfo return true; } + if (item.SupportsLocalMetadata && video != null && !video.IsPlaceHolder + && !video.AudioFiles.SequenceEqual( + _audioResolver.GetExternalAudioFiles(video, directoryService, false), StringComparer.Ordinal)) + { + _logger.LogDebug("Refreshing {0} due to external audio change.", item.Path); + return true; + } + return false; } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 4ab15f60e2..8db095416c 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -214,6 +214,8 @@ namespace MediaBrowser.Providers.MediaInfo await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); + AddExternalAudio(video, mediaStreams, options, cancellationToken); + var libraryOptions = _libraryManager.GetLibraryOptions(video); if (mediaInfo != null) @@ -574,6 +576,29 @@ namespace MediaBrowser.Providers.MediaInfo currentStreams.AddRange(externalSubtitleStreams); } + /// + /// Adds the external audio. + /// + /// The video. + /// The current streams. + /// The refreshOptions. + /// The cancellation token. + private void AddExternalAudio( + Video video, + List currentStreams, + MetadataRefreshOptions options, + CancellationToken cancellationToken) + { + var audioResolver = new AudioResolver(_localization, _mediaEncoder, cancellationToken); + + var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1); + var externalAudioStreams = audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, false); + + video.AudioFiles = externalAudioStreams.Select(i => i.Path).ToArray(); + + currentStreams.AddRange(externalAudioStreams); + } + /// /// Creates dummy chapters. /// From c1a8385c9ceb70d9ad4301e7032e1719fe1bdc23 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Wed, 24 Nov 2021 21:53:36 +0100 Subject: [PATCH 13/73] Shorten calculation of audio startIndex in MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 8db095416c..2d49e43cad 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -591,7 +591,7 @@ namespace MediaBrowser.Providers.MediaInfo { var audioResolver = new AudioResolver(_localization, _mediaEncoder, cancellationToken); - var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1); + var startIndex = currentStreams.Count == 0 ? 0 : currentStreams.Max(i => i.Index) + 1; var externalAudioStreams = audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, false); video.AudioFiles = externalAudioStreams.Select(i => i.Path).ToArray(); From 5e91f50c437adcf28cc80eba970c87fddf4f0c9f Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Sat, 27 Nov 2021 12:10:49 +0100 Subject: [PATCH 14/73] Update CONTRIBUTORS.md --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 5c4a031bc1..d52e133249 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -150,6 +150,7 @@ - [ianjazz246](https://github.com/ianjazz246) - [peterspenler](https://github.com/peterspenler) - [MBR-0001](https://github.com/MBR-0001) + - [jonas-resch](https://github.com/jonas-resch) # Emby Contributors From a68e58556c49ee9bc1c27fac696ffc9170c95e84 Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Sat, 27 Nov 2021 12:10:57 +0100 Subject: [PATCH 15/73] Implement code feedback - Rewrite AudioResolver - Use async & await instead of .Result - Add support for audio containers with multiple audio streams (e.g. mka) - Fix bug when using external subtitle and external audio streams at the same time --- .../MediaEncoding/EncodingHelper.cs | 21 +- .../MediaInfo/AudioResolver.cs | 279 ++++++------------ .../MediaInfo/FFProbeProvider.cs | 16 +- .../MediaInfo/FFProbeVideoInfo.cs | 16 +- 4 files changed, 118 insertions(+), 214 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5326ecd20c..5695ee2dbb 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -678,12 +678,6 @@ namespace MediaBrowser.Controller.MediaEncoding arg.Append("-i ") .Append(GetInputPathArgument(state)); - if (state.AudioStream.IsExternal) - { - arg.Append(" -i ") - .Append(string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", state.AudioStream.Path)); - } - if (state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) @@ -702,6 +696,12 @@ namespace MediaBrowser.Controller.MediaEncoding arg.Append(" -i \"").Append(subtitlePath).Append('\"'); } + if (state.AudioStream != null && state.AudioStream.IsExternal) + { + arg.Append(" -i ") + .Append(string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", state.AudioStream.Path)); + } + return arg.ToString(); } @@ -2007,7 +2007,14 @@ namespace MediaBrowser.Controller.MediaEncoding { if (state.AudioStream.IsExternal) { - args += " -map 1:a"; + int externalAudioMapIndex = state.SubtitleStream != null && state.SubtitleStream.IsExternal ? 2 : 1; + int externalAudioStream = state.MediaSource.MediaStreams.Where(i => i.Path == state.AudioStream.Path).ToList().IndexOf(state.AudioStream); + + args += string.Format( + CultureInfo.InvariantCulture, + " -map {0}:{1}", + externalAudioMapIndex, + externalAudioStream); } else { diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index fce2fa5512..8d5f8d86ec 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -1,13 +1,13 @@ -#nullable disable - #pragma warning disable CA1002, CS1591 using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; +using Emby.Naming.Audio; +using Emby.Naming.Common; +using Jellyfin.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Providers; @@ -21,24 +21,15 @@ namespace MediaBrowser.Providers.MediaInfo { public class AudioResolver { - private readonly ILocalizationManager _localization; - - private readonly IMediaEncoder _mediaEncoder; - - private readonly CancellationToken _cancellationToken; - - public AudioResolver(ILocalizationManager localization, IMediaEncoder mediaEncoder, CancellationToken cancellationToken = default) - { - _localization = localization; - _mediaEncoder = mediaEncoder; - _cancellationToken = cancellationToken; - } - - public List GetExternalAudioStreams( + public async Task> GetExternalAudioStreams( Video video, int startIndex, IDirectoryService directoryService, - bool clearCache) + NamingOptions namingOptions, + bool clearCache, + ILocalizationManager localizationManager, + IMediaEncoder mediaEncoder, + CancellationToken cancellationToken) { var streams = new List(); @@ -47,198 +38,117 @@ namespace MediaBrowser.Providers.MediaInfo return streams; } - AddExternalAudioStreams(streams, video.ContainingFolderPath, video.Path, startIndex, directoryService, clearCache); + List paths = GetExternalAudioFiles(video, directoryService, namingOptions, clearCache); - startIndex += streams.Count; - - string folder = video.GetInternalMetadataPath(); - - if (!Directory.Exists(folder)) - { - return streams; - } - - try - { - AddExternalAudioStreams(streams, folder, video.Path, startIndex, directoryService, clearCache); - } - catch (IOException) - { - } + await AddExternalAudioStreams(streams, paths, startIndex, localizationManager, mediaEncoder, cancellationToken); return streams; } - public IEnumerable GetExternalAudioFiles( + public List GetExternalAudioFiles( Video video, IDirectoryService directoryService, + NamingOptions namingOptions, bool clearCache) { + List paths = new List(); + if (!video.IsFileProtocol) { - yield break; + return paths; } - var streams = GetExternalAudioStreams(video, 0, directoryService, clearCache); + paths.AddRange(GetAudioFilesFromFolder(video.ContainingFolderPath, video.Path, directoryService, namingOptions, clearCache)); + paths.AddRange(GetAudioFilesFromFolder(video.GetInternalMetadataPath(), video.Path, directoryService, namingOptions, clearCache)); - foreach (var stream in streams) - { - yield return stream.Path; - } + return paths; } - public void AddExternalAudioStreams( - List streams, - string videoPath, - int startIndex, - IReadOnlyList files) + private List GetAudioFilesFromFolder( + string folder, + string videoFileName, + IDirectoryService directoryService, + NamingOptions namingOptions, + bool clearCache) { - var videoFileNameWithoutExtension = NormalizeFilenameForAudioComparison(videoPath); + List paths = new List(); + string videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(videoFileName); - for (var i = 0; i < files.Count; i++) + if (!Directory.Exists(folder)) { + return paths; + } - var fullName = files[i]; - var extension = Path.GetExtension(fullName.AsSpan()); - if (!IsAudioExtension(extension)) + var files = directoryService.GetFilePaths(folder, clearCache, true); + for (int i = 0; i < files.Count; i++) + { + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(files[i]); + + if (!AudioFileParser.IsAudioFile(files[i], namingOptions)) { continue; } - Model.MediaInfo.MediaInfo mediaInfo = GetMediaInfo(fullName).Result; - MediaStream mediaStream = mediaInfo.MediaStreams.First(); - mediaStream.Index = startIndex++; - mediaStream.Type = MediaStreamType.Audio; - mediaStream.IsExternal = true; - mediaStream.Path = fullName; - mediaStream.IsDefault = false; - mediaStream.Title = null; - - var fileNameWithoutExtension = NormalizeFilenameForAudioComparison(fullName); - // The audio filename must either be equal to the video filename or start with the video filename followed by a dot - if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) - { - mediaStream.Path = fullName; - } - else if (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length + if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) || + (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' - && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))) { + paths.Add(files[i]); + } + } - // Support xbmc naming conventions - 300.spanish.m4a - var languageSpan = fileNameWithoutExtension; - while (languageSpan.Length > 0) + return paths; + } + + public async Task AddExternalAudioStreams( + List streams, + List paths, + int startIndex, + ILocalizationManager localizationManager, + IMediaEncoder mediaEncoder, + CancellationToken cancellationToken) + { + foreach (string path in paths) + { + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path); + Model.MediaInfo.MediaInfo mediaInfo = await GetMediaInfo(path, mediaEncoder, cancellationToken); + + foreach (MediaStream mediaStream in mediaInfo.MediaStreams) + { + mediaStream.Index = startIndex++; + mediaStream.Type = MediaStreamType.Audio; + mediaStream.IsExternal = true; + mediaStream.Path = path; + mediaStream.IsDefault = false; + mediaStream.Title = null; + + if (mediaStream.Language == null) { - var lastDot = languageSpan.LastIndexOf('.'); - var currentSlice = languageSpan[lastDot..]; - languageSpan = languageSpan[(lastDot + 1)..]; - break; + // Try to translate to three character code + // Be flexible and check against both the full and three character versions + var language = StringExtensions.RightPart(fileNameWithoutExtension, '.').ToString(); + + if (language != fileNameWithoutExtension) + { + var culture = localizationManager.FindLanguageInfo(language); + + language = culture == null ? language : culture.ThreeLetterISOLanguageName; + mediaStream.Language = language; + } } - // Try to translate to three character code - // Be flexible and check against both the full and three character versions - var language = languageSpan.ToString(); - var culture = _localization.FindLanguageInfo(language); - - language = culture == null ? language : culture.ThreeLetterISOLanguageName; - mediaStream.Language = language; + streams.Add(mediaStream); } - else - { - continue; - } - - mediaStream.Codec = extension.TrimStart('.').ToString().ToLowerInvariant(); - - streams.Add(mediaStream); } } - private static bool IsAudioExtension(ReadOnlySpan extension) + private Task GetMediaInfo(string path, IMediaEncoder mediaEncoder, CancellationToken cancellationToken) { - String[] audioExtensions = new[] - { - ".nsv", - ".m4a", - ".flac", - ".aac", - ".strm", - ".pls", - ".rm", - ".mpa", - ".wav", - ".wma", - ".ogg", - ".opus", - ".mp3", - ".mp2", - ".mod", - ".amf", - ".669", - ".dmf", - ".dsm", - ".far", - ".gdm", - ".imf", - ".it", - ".m15", - ".med", - ".okt", - ".s3m", - ".stm", - ".sfx", - ".ult", - ".uni", - ".xm", - ".sid", - ".ac3", - ".dts", - ".cue", - ".aif", - ".aiff", - ".ape", - ".mac", - ".mpc", - ".mp+", - ".mpp", - ".shn", - ".wv", - ".nsf", - ".spc", - ".gym", - ".adplug", - ".adx", - ".dsp", - ".adp", - ".ymf", - ".ast", - ".afc", - ".hps", - ".xsp", - ".acc", - ".m4b", - ".oga", - ".dsf", - ".mka" - }; + cancellationToken.ThrowIfCancellationRequested(); - foreach (String audioExtension in audioExtensions) - { - if (extension.Equals(audioExtension, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; - } - - private Task GetMediaInfo(string path) - { - _cancellationToken.ThrowIfCancellationRequested(); - - return _mediaEncoder.GetMediaInfo( + return mediaEncoder.GetMediaInfo( new MediaInfoRequest { MediaType = DlnaProfileType.Audio, @@ -248,28 +158,7 @@ namespace MediaBrowser.Providers.MediaInfo Protocol = MediaProtocol.File } }, - _cancellationToken); - } - - private static ReadOnlySpan NormalizeFilenameForAudioComparison(string filename) - { - // Try to account for sloppy file naming - filename = filename.Replace("_", string.Empty, StringComparison.Ordinal); - filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal); - return Path.GetFileNameWithoutExtension(filename.AsSpan()); - } - - private void AddExternalAudioStreams( - List streams, - string folder, - string videoPath, - int startIndex, - IDirectoryService directoryService, - bool clearCache) - { - var files = directoryService.GetFilePaths(folder, clearCache, true); - - AddExternalAudioStreams(streams, videoPath, startIndex, files); + cancellationToken); } } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 1e7fcf2d20..98909c94ec 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Emby.Naming.Common; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -50,10 +51,10 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IMediaSourceManager _mediaSourceManager; private readonly SubtitleResolver _subtitleResolver; - private readonly AudioResolver _audioResolver; - private readonly Task _cachedTask = Task.FromResult(ItemUpdateType.None); + private readonly NamingOptions _namingOptions; + public FFProbeProvider( ILogger logger, IMediaSourceManager mediaSourceManager, @@ -65,7 +66,8 @@ namespace MediaBrowser.Providers.MediaInfo IServerConfigurationManager config, ISubtitleManager subtitleManager, IChapterManager chapterManager, - ILibraryManager libraryManager) + ILibraryManager libraryManager, + NamingOptions namingOptions) { _logger = logger; _mediaEncoder = mediaEncoder; @@ -78,9 +80,9 @@ namespace MediaBrowser.Providers.MediaInfo _chapterManager = chapterManager; _libraryManager = libraryManager; _mediaSourceManager = mediaSourceManager; + _namingOptions = namingOptions; _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager); - _audioResolver = new AudioResolver(BaseItem.LocalizationManager, mediaEncoder); } public string Name => "ffprobe"; @@ -114,9 +116,10 @@ namespace MediaBrowser.Providers.MediaInfo return true; } + AudioResolver audioResolver = new AudioResolver(); if (item.SupportsLocalMetadata && video != null && !video.IsPlaceHolder && !video.AudioFiles.SequenceEqual( - _audioResolver.GetExternalAudioFiles(video, directoryService, false), StringComparer.Ordinal)) + audioResolver.GetExternalAudioFiles(video, directoryService, _namingOptions, false), StringComparer.Ordinal)) { _logger.LogDebug("Refreshing {0} due to external audio change.", item.Path); return true; @@ -199,7 +202,8 @@ namespace MediaBrowser.Providers.MediaInfo _config, _subtitleManager, _chapterManager, - _libraryManager); + _libraryManager, + _namingOptions); return prober.ProbeVideo(item, options, cancellationToken); } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 2d49e43cad..39950db70f 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using DvdLib.Ifo; +using Emby.Naming.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; @@ -44,6 +45,7 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ISubtitleManager _subtitleManager; private readonly IChapterManager _chapterManager; private readonly ILibraryManager _libraryManager; + private readonly NamingOptions _namingOptions; private readonly IMediaSourceManager _mediaSourceManager; private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks; @@ -59,7 +61,8 @@ namespace MediaBrowser.Providers.MediaInfo IServerConfigurationManager config, ISubtitleManager subtitleManager, IChapterManager chapterManager, - ILibraryManager libraryManager) + ILibraryManager libraryManager, + NamingOptions namingOptions) { _logger = logger; _mediaEncoder = mediaEncoder; @@ -71,6 +74,7 @@ namespace MediaBrowser.Providers.MediaInfo _subtitleManager = subtitleManager; _chapterManager = chapterManager; _libraryManager = libraryManager; + _namingOptions = namingOptions; _mediaSourceManager = mediaSourceManager; } @@ -214,7 +218,7 @@ namespace MediaBrowser.Providers.MediaInfo await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); - AddExternalAudio(video, mediaStreams, options, cancellationToken); + await AddExternalAudio(video, mediaStreams, options, cancellationToken); var libraryOptions = _libraryManager.GetLibraryOptions(video); @@ -583,18 +587,18 @@ namespace MediaBrowser.Providers.MediaInfo /// The current streams. /// The refreshOptions. /// The cancellation token. - private void AddExternalAudio( + private async Task AddExternalAudio( Video video, List currentStreams, MetadataRefreshOptions options, CancellationToken cancellationToken) { - var audioResolver = new AudioResolver(_localization, _mediaEncoder, cancellationToken); + var audioResolver = new AudioResolver(); var startIndex = currentStreams.Count == 0 ? 0 : currentStreams.Max(i => i.Index) + 1; - var externalAudioStreams = audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, false); + var externalAudioStreams = await audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, _namingOptions, false, _localization, _mediaEncoder, cancellationToken); - video.AudioFiles = externalAudioStreams.Select(i => i.Path).ToArray(); + video.AudioFiles = externalAudioStreams.Select(i => i.Path).Distinct().ToArray(); currentStreams.AddRange(externalAudioStreams); } From f1862f9b1a9d6daa0315308671cfb6d0fdd6a989 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:50:24 +0100 Subject: [PATCH 16/73] Add ConfigureAwait false to MediaBrowser.Providers/MediaInfo/AudioResolver.cs Co-authored-by: Cody Robibero --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 8d5f8d86ec..2a50dc5d11 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -40,7 +40,7 @@ namespace MediaBrowser.Providers.MediaInfo List paths = GetExternalAudioFiles(video, directoryService, namingOptions, clearCache); - await AddExternalAudioStreams(streams, paths, startIndex, localizationManager, mediaEncoder, cancellationToken); + await AddExternalAudioStreams(streams, paths, startIndex, localizationManager, mediaEncoder, cancellationToken).ConfigureAwait(false); return streams; } From a3c5afa443dca5cfd90ff1a33b7af9dbfe751ff4 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:50:39 +0100 Subject: [PATCH 17/73] Add ConfigureAwait false MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs Co-authored-by: Cody Robibero --- MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 39950db70f..7450205993 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -218,7 +218,7 @@ namespace MediaBrowser.Providers.MediaInfo await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); - await AddExternalAudio(video, mediaStreams, options, cancellationToken); + await AddExternalAudio(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); var libraryOptions = _libraryManager.GetLibraryOptions(video); From 9433072f90593a43d2faa4eb645eb521a257205c Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:52:03 +0100 Subject: [PATCH 18/73] Only search in video folder for external audio files Don't search in video metadata folder since audio files won't be stored there Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 2a50dc5d11..2f73819834 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -59,7 +59,6 @@ namespace MediaBrowser.Providers.MediaInfo } paths.AddRange(GetAudioFilesFromFolder(video.ContainingFolderPath, video.Path, directoryService, namingOptions, clearCache)); - paths.AddRange(GetAudioFilesFromFolder(video.GetInternalMetadataPath(), video.Path, directoryService, namingOptions, clearCache)); return paths; } From 61b191d34556a0dad260344e83b9f40ac12f28ce Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:55:19 +0100 Subject: [PATCH 19/73] Fix indentation in MediaBrowser.Providers/MediaInfo/AudioResolver.cs If statement which checks if filename of audio and video file match or if audio file starts with video filename Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 2f73819834..481a82df49 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -89,10 +89,10 @@ namespace MediaBrowser.Providers.MediaInfo } // The audio filename must either be equal to the video filename or start with the video filename followed by a dot - if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) || - (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length - && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' - && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))) + if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) + || (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length + && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' + && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))) { paths.Add(files[i]); } From d016d483ae5ab79f6f1171ce7f6ca99b8cd91cf0 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:56:17 +0100 Subject: [PATCH 20/73] Change return type from Task> to Task> in MediaBrowser.Providers/MediaInfo/AudioResolver.cs Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 481a82df49..bd778684ed 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.Providers.MediaInfo { public class AudioResolver { - public async Task> GetExternalAudioStreams( + public async Task> GetExternalAudioStreams( Video video, int startIndex, IDirectoryService directoryService, From bbf1399826f92241a653adbb808a4cc7ea6a5542 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:57:53 +0100 Subject: [PATCH 21/73] Check language for null or empty instead of only null in MediaBrowser.Providers/MediaInfo/AudioResolver.cs Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index bd778684ed..54d8525778 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -123,7 +123,7 @@ namespace MediaBrowser.Providers.MediaInfo mediaStream.IsDefault = false; mediaStream.Title = null; - if (mediaStream.Language == null) + if (string.IsNullOrEmpty(mediaStream.Language)) { // Try to translate to three character code // Be flexible and check against both the full and three character versions From 9d34d6339a2d677139573ada98b92318de298653 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:58:37 +0100 Subject: [PATCH 22/73] Change return type from List to IEnumerable in MediaBrowser.Providers/MediaInfo/AudioResolver.cs Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 54d8525778..ccac450f6c 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -45,7 +45,7 @@ namespace MediaBrowser.Providers.MediaInfo return streams; } - public List GetExternalAudioFiles( + public IEnumerable GetExternalAudioFiles( Video video, IDirectoryService directoryService, NamingOptions namingOptions, From 0894a6193f025a9cec5c735226a8487caa2bc66b Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Sun, 28 Nov 2021 14:03:52 +0100 Subject: [PATCH 23/73] Implement coding standards from 2nd code feedback --- .../MediaEncoding/EncodingHelper.cs | 3 +- .../MediaInfo/AudioResolver.cs | 146 ++++++++---------- .../MediaInfo/FFProbeProvider.cs | 15 +- .../MediaInfo/FFProbeVideoInfo.cs | 21 +-- 4 files changed, 82 insertions(+), 103 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5695ee2dbb..5712303123 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -698,8 +698,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.AudioStream != null && state.AudioStream.IsExternal) { - arg.Append(" -i ") - .Append(string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", state.AudioStream.Path)); + arg.Append(" -i \"").Append(state.AudioStream.Path).Append("\""); } return arg.ToString(); diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index ccac450f6c..d23afdc3b9 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -21,98 +21,37 @@ namespace MediaBrowser.Providers.MediaInfo { public class AudioResolver { - public async Task> GetExternalAudioStreams( + private readonly ILocalizationManager _localizationManager; + private readonly IMediaEncoder _mediaEncoder; + private readonly NamingOptions _namingOptions; + + public AudioResolver( + ILocalizationManager localizationManager, + IMediaEncoder mediaEncoder, + NamingOptions namingOptions) + { + _localizationManager = localizationManager; + _mediaEncoder = mediaEncoder; + _namingOptions = namingOptions; + } + + public async IAsyncEnumerable GetExternalAudioStreams( Video video, int startIndex, IDirectoryService directoryService, - NamingOptions namingOptions, bool clearCache, - ILocalizationManager localizationManager, - IMediaEncoder mediaEncoder, CancellationToken cancellationToken) { - var streams = new List(); - if (!video.IsFileProtocol) { - return streams; + yield break; } - List paths = GetExternalAudioFiles(video, directoryService, namingOptions, clearCache); - - await AddExternalAudioStreams(streams, paths, startIndex, localizationManager, mediaEncoder, cancellationToken).ConfigureAwait(false); - - return streams; - } - - public IEnumerable GetExternalAudioFiles( - Video video, - IDirectoryService directoryService, - NamingOptions namingOptions, - bool clearCache) - { - List paths = new List(); - - if (!video.IsFileProtocol) - { - return paths; - } - - paths.AddRange(GetAudioFilesFromFolder(video.ContainingFolderPath, video.Path, directoryService, namingOptions, clearCache)); - - return paths; - } - - private List GetAudioFilesFromFolder( - string folder, - string videoFileName, - IDirectoryService directoryService, - NamingOptions namingOptions, - bool clearCache) - { - List paths = new List(); - string videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(videoFileName); - - if (!Directory.Exists(folder)) - { - return paths; - } - - var files = directoryService.GetFilePaths(folder, clearCache, true); - for (int i = 0; i < files.Count; i++) - { - string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(files[i]); - - if (!AudioFileParser.IsAudioFile(files[i], namingOptions)) - { - continue; - } - - // The audio filename must either be equal to the video filename or start with the video filename followed by a dot - if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) - || (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length - && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' - && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))) - { - paths.Add(files[i]); - } - } - - return paths; - } - - public async Task AddExternalAudioStreams( - List streams, - List paths, - int startIndex, - ILocalizationManager localizationManager, - IMediaEncoder mediaEncoder, - CancellationToken cancellationToken) - { + IEnumerable paths = GetExternalAudioFiles(video, directoryService, clearCache); foreach (string path in paths) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path); - Model.MediaInfo.MediaInfo mediaInfo = await GetMediaInfo(path, mediaEncoder, cancellationToken); + Model.MediaInfo.MediaInfo mediaInfo = await GetMediaInfo(path, cancellationToken); foreach (MediaStream mediaStream in mediaInfo.MediaStreams) { @@ -131,23 +70,64 @@ namespace MediaBrowser.Providers.MediaInfo if (language != fileNameWithoutExtension) { - var culture = localizationManager.FindLanguageInfo(language); + var culture = _localizationManager.FindLanguageInfo(language); language = culture == null ? language : culture.ThreeLetterISOLanguageName; mediaStream.Language = language; } } - streams.Add(mediaStream); + yield return mediaStream; } } } - private Task GetMediaInfo(string path, IMediaEncoder mediaEncoder, CancellationToken cancellationToken) + public IEnumerable GetExternalAudioFiles( + Video video, + IDirectoryService directoryService, + bool clearCache) + { + if (!video.IsFileProtocol) + { + yield break; + } + + // Check if video folder exists + string folder = video.ContainingFolderPath; + if (!Directory.Exists(folder)) + { + yield break; + } + + string videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path); + + var files = directoryService.GetFilePaths(folder, clearCache, true); + for (int i = 0; i < files.Count; i++) + { + string file = files[i]; + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file); + + if (!AudioFileParser.IsAudioFile(file, _namingOptions)) + { + continue; + } + + // The audio filename must either be equal to the video filename or start with the video filename followed by a dot + if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) + || (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length + && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' + && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))) + { + yield return file; + } + } + } + + private Task GetMediaInfo(string path, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - return mediaEncoder.GetMediaInfo( + return _mediaEncoder.GetMediaInfo( new MediaInfoRequest { MediaType = DlnaProfileType.Audio, diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 98909c94ec..392641468e 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -50,10 +50,9 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ILibraryManager _libraryManager; private readonly IMediaSourceManager _mediaSourceManager; private readonly SubtitleResolver _subtitleResolver; - private readonly Task _cachedTask = Task.FromResult(ItemUpdateType.None); - private readonly NamingOptions _namingOptions; + private readonly AudioResolver _audioResolver; public FFProbeProvider( ILogger logger, @@ -83,6 +82,7 @@ namespace MediaBrowser.Providers.MediaInfo _namingOptions = namingOptions; _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager); + _audioResolver = new AudioResolver(_localization, _mediaEncoder, namingOptions); } public string Name => "ffprobe"; @@ -102,7 +102,7 @@ namespace MediaBrowser.Providers.MediaInfo var file = directoryService.GetFile(path); if (file != null && file.LastWriteTimeUtc != item.DateModified) { - _logger.LogDebug("Refreshing {0} due to date modified timestamp change.", path); + _logger.LogDebug("Refreshing {ItemPath} due to date modified timestamp change.", path); return true; } } @@ -112,16 +112,15 @@ namespace MediaBrowser.Providers.MediaInfo && !video.SubtitleFiles.SequenceEqual( _subtitleResolver.GetExternalSubtitleFiles(video, directoryService, false), StringComparer.Ordinal)) { - _logger.LogDebug("Refreshing {0} due to external subtitles change.", item.Path); + _logger.LogDebug("Refreshing {ItemPath} due to external subtitles change.", item.Path); return true; } - AudioResolver audioResolver = new AudioResolver(); if (item.SupportsLocalMetadata && video != null && !video.IsPlaceHolder && !video.AudioFiles.SequenceEqual( - audioResolver.GetExternalAudioFiles(video, directoryService, _namingOptions, false), StringComparer.Ordinal)) + _audioResolver.GetExternalAudioFiles(video, directoryService, false), StringComparer.Ordinal)) { - _logger.LogDebug("Refreshing {0} due to external audio change.", item.Path); + _logger.LogDebug("Refreshing {ItemPath} due to external audio change.", item.Path); return true; } @@ -203,7 +202,7 @@ namespace MediaBrowser.Providers.MediaInfo _subtitleManager, _chapterManager, _libraryManager, - _namingOptions); + _audioResolver); return prober.ProbeVideo(item, options, cancellationToken); } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 7450205993..b31f0ed23d 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -10,7 +10,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using DvdLib.Ifo; -using Emby.Naming.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; @@ -45,7 +44,7 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ISubtitleManager _subtitleManager; private readonly IChapterManager _chapterManager; private readonly ILibraryManager _libraryManager; - private readonly NamingOptions _namingOptions; + private readonly AudioResolver _audioResolver; private readonly IMediaSourceManager _mediaSourceManager; private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks; @@ -62,7 +61,7 @@ namespace MediaBrowser.Providers.MediaInfo ISubtitleManager subtitleManager, IChapterManager chapterManager, ILibraryManager libraryManager, - NamingOptions namingOptions) + AudioResolver audioResolver) { _logger = logger; _mediaEncoder = mediaEncoder; @@ -74,7 +73,7 @@ namespace MediaBrowser.Providers.MediaInfo _subtitleManager = subtitleManager; _chapterManager = chapterManager; _libraryManager = libraryManager; - _namingOptions = namingOptions; + _audioResolver = audioResolver; _mediaSourceManager = mediaSourceManager; } @@ -218,7 +217,7 @@ namespace MediaBrowser.Providers.MediaInfo await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); - await AddExternalAudio(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); + await AddExternalAudio(video, mediaStreams, options, cancellationToken); var libraryOptions = _libraryManager.GetLibraryOptions(video); @@ -593,14 +592,16 @@ namespace MediaBrowser.Providers.MediaInfo MetadataRefreshOptions options, CancellationToken cancellationToken) { - var audioResolver = new AudioResolver(); - var startIndex = currentStreams.Count == 0 ? 0 : currentStreams.Max(i => i.Index) + 1; - var externalAudioStreams = await audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, _namingOptions, false, _localization, _mediaEncoder, cancellationToken); + var externalAudioStreams = _audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, false, cancellationToken); - video.AudioFiles = externalAudioStreams.Select(i => i.Path).Distinct().ToArray(); + await foreach (MediaStream externalAudioStream in externalAudioStreams) + { + currentStreams.Add(externalAudioStream); + } - currentStreams.AddRange(externalAudioStreams); + // Select all external audio file paths + video.AudioFiles = currentStreams.Where(i => i.Type == MediaStreamType.Audio && i.IsExternal).Select(i => i.Path).Distinct().ToArray(); } /// From b5b994b22f8ec8cf0fd10f67d78d66dd12b3e21c Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Sun, 28 Nov 2021 14:20:12 +0100 Subject: [PATCH 24/73] Fix compiler warning due to missing EnumeratorCancellation attribute --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index d23afdc3b9..869512f76c 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Emby.Naming.Audio; @@ -40,8 +41,11 @@ namespace MediaBrowser.Providers.MediaInfo int startIndex, IDirectoryService directoryService, bool clearCache, - CancellationToken cancellationToken) + [EnumeratorCancellation] CancellationToken cancellationToken) { + + cancellationToken.ThrowIfCancellationRequested(); + if (!video.IsFileProtocol) { yield break; From c61b9ef05a4a49a90378e9f81275eb84c2b55eed Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Tue, 30 Nov 2021 19:52:44 +0100 Subject: [PATCH 25/73] Fix warning due to new line after opening bracket --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 869512f76c..35351665a3 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -43,7 +43,6 @@ namespace MediaBrowser.Providers.MediaInfo bool clearCache, [EnumeratorCancellation] CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); if (!video.IsFileProtocol) From 1a356908346fbfe1ba0cf438b2a5c8248fd3f371 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Tue, 30 Nov 2021 20:44:16 +0100 Subject: [PATCH 26/73] Don't disable warnings in MediaBrowser.Providers/MediaInfo/AudioResolver.cs Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 35351665a3..795c4d6ce5 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA1002, CS1591 - using System; using System.Collections.Generic; using System.IO; From 0d8170cedb1fd711e5c2d09d458467cccdd7874a Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Tue, 30 Nov 2021 20:44:57 +0100 Subject: [PATCH 27/73] Move variable in MediaBrowser.Providers/MediaInfo/AudioResolver.cs Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 795c4d6ce5..6d1486698f 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -106,13 +106,12 @@ namespace MediaBrowser.Providers.MediaInfo for (int i = 0; i < files.Count; i++) { string file = files[i]; - string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file); - if (!AudioFileParser.IsAudioFile(file, _namingOptions)) { continue; } + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file); // The audio filename must either be equal to the video filename or start with the video filename followed by a dot if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) || (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length From a9a53dc6579184edfb70ce289c06176e769d5280 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Tue, 30 Nov 2021 20:45:21 +0100 Subject: [PATCH 28/73] Add ConfigureAwait true in MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index b31f0ed23d..542e56256e 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -217,7 +217,7 @@ namespace MediaBrowser.Providers.MediaInfo await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); - await AddExternalAudio(video, mediaStreams, options, cancellationToken); + await AddExternalAudio(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); var libraryOptions = _libraryManager.GetLibraryOptions(video); From 7b500480201cef84f5d794c9f6319cccf8b7b03b Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Tue, 30 Nov 2021 20:45:47 +0100 Subject: [PATCH 29/73] Add ConfigureAwait true in MediaBrowser.Providers/MediaInfo/AudioResolver.cs Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 6d1486698f..b6f3ac1cc1 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -52,7 +52,7 @@ namespace MediaBrowser.Providers.MediaInfo foreach (string path in paths) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path); - Model.MediaInfo.MediaInfo mediaInfo = await GetMediaInfo(path, cancellationToken); + Model.MediaInfo.MediaInfo mediaInfo = await GetMediaInfo(path, cancellationToken).ConfigureAwait(false); foreach (MediaStream mediaStream in mediaInfo.MediaStreams) { From 6bbfcf1906b7a35da3db4954f9a3762bca9f3a93 Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Tue, 30 Nov 2021 21:05:43 +0100 Subject: [PATCH 30/73] Add documentation to AudioResolver class --- .../MediaInfo/AudioResolver.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index b6f3ac1cc1..20dee834ff 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -18,12 +18,21 @@ using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Providers.MediaInfo { + /// + /// Resolves external audios for videos. + /// public class AudioResolver { private readonly ILocalizationManager _localizationManager; private readonly IMediaEncoder _mediaEncoder; private readonly NamingOptions _namingOptions; + /// + /// Initializes a new instance of the class. + /// + /// The localization manager. + /// The media encoder. + /// The naming options. public AudioResolver( ILocalizationManager localizationManager, IMediaEncoder mediaEncoder, @@ -34,6 +43,15 @@ namespace MediaBrowser.Providers.MediaInfo _namingOptions = namingOptions; } + /// + /// Returns the audio streams found in the external audio files for the given video. + /// + /// The video to get the external audio streams from. + /// The stream index to start adding audio streams at. + /// The directory service to search for files. + /// True if the directory service cache should be cleared before searching. + /// The cancellation token to cancel operation. + /// A list of external audio streams. public async IAsyncEnumerable GetExternalAudioStreams( Video video, int startIndex, @@ -83,6 +101,13 @@ namespace MediaBrowser.Providers.MediaInfo } } + /// + /// Returns the external audio file paths for the given video. + /// + /// The video to get the external audio file paths from. + /// The directory service to search for files. + /// True if the directory service cache should be cleared before searching. + /// A list of external audio file paths. public IEnumerable GetExternalAudioFiles( Video video, IDirectoryService directoryService, @@ -123,6 +148,12 @@ namespace MediaBrowser.Providers.MediaInfo } } + /// + /// Returns the media info of the given audio file. + /// + /// The path to the audio file. + /// The cancellation token to cancel operation. + /// The media info for the given audio file. private Task GetMediaInfo(string path, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); From 180e2dc329434a68a5fe3a8ac05591d0a7c898f8 Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Wed, 1 Dec 2021 21:05:43 +0100 Subject: [PATCH 31/73] Prevent crashes in specific scenarios --- MediaBrowser.Controller/Entities/Video.cs | 1 + MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 56e955adad..8e0593507a 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -33,6 +33,7 @@ namespace MediaBrowser.Controller.Entities AdditionalParts = Array.Empty(); LocalAlternateVersions = Array.Empty(); SubtitleFiles = Array.Empty(); + AudioFiles = Array.Empty(); LinkedAlternateVersions = Array.Empty(); } diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 20dee834ff..bec8ee34a6 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -131,7 +131,7 @@ namespace MediaBrowser.Providers.MediaInfo for (int i = 0; i < files.Count; i++) { string file = files[i]; - if (!AudioFileParser.IsAudioFile(file, _namingOptions)) + if (string.Equals(video.Path, file, StringComparison.OrdinalIgnoreCase) || !AudioFileParser.IsAudioFile(file, _namingOptions)) { continue; } From 120828d8d03da08a55edfbce5bfe1463bf06eae3 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Fri, 3 Dec 2021 19:18:43 +0100 Subject: [PATCH 32/73] Replace escaped quote string with quote character in MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs Co-authored-by: Cody Robibero --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5712303123..92b345f126 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -698,7 +698,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.AudioStream != null && state.AudioStream.IsExternal) { - arg.Append(" -i \"").Append(state.AudioStream.Path).Append("\""); + arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"'); } return arg.ToString(); From 99a48554a618302b4fe70ef1fd3d7fd06096c70e Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Fri, 3 Dec 2021 19:19:22 +0100 Subject: [PATCH 33/73] Optimize calculation of external audio stream index in MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs Co-authored-by: Cody Robibero --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 92b345f126..91f6654bb7 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2007,7 +2007,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.AudioStream.IsExternal) { int externalAudioMapIndex = state.SubtitleStream != null && state.SubtitleStream.IsExternal ? 2 : 1; - int externalAudioStream = state.MediaSource.MediaStreams.Where(i => i.Path == state.AudioStream.Path).ToList().IndexOf(state.AudioStream); + int externalAudioStream = state.MediaSource.MediaStreams.FindIndex(i => i.Path == state.AudioStream.Path); args += string.Format( CultureInfo.InvariantCulture, From 3176a4ddd956a16f95b14ccedf2f6aa344019ab9 Mon Sep 17 00:00:00 2001 From: matthiasdv Date: Mon, 6 Dec 2021 22:40:00 +0100 Subject: [PATCH 34/73] add more hardening to systemd service --- debian/jellyfin.service | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/debian/jellyfin.service b/debian/jellyfin.service index e215a85362..071f949dd9 100644 --- a/debian/jellyfin.service +++ b/debian/jellyfin.service @@ -13,7 +13,20 @@ TimeoutSec = 15 NoNewPrivileges=true SystemCallArchitectures=native RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK -ProtectKernelModules=True +RestrictNamespaces=true +RestrictRealtime=true +RestrictSUIDSGID=true +ProtectClock=true +ProtectControlGroups=true +ProtectHostname=true +ProtectKernelLogs=true +ProtectKernelModules=true +ProtectKernelTunables=true +LockPersonality=true +PrivateTmp=true +PrivateDevices=false +PrivateUsers=true +RemoveIPC=true SystemCallFilter=~@clock SystemCallFilter=~@aio SystemCallFilter=~@chown From a327b43ab7faceadb555890c41f103008fc00737 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 7 Dec 2021 20:28:51 +0100 Subject: [PATCH 35/73] Update MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs --- MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 8445a12aac..fc59e410fd 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -45,7 +45,6 @@ namespace MediaBrowser.Providers.MediaInfo private readonly FFProbeAudioInfo _audioProber; private readonly Task _cachedTask = Task.FromResult(ItemUpdateType.None); - private readonly NamingOptions _namingOptions; public FFProbeProvider( ILogger logger, From 3513f5a84bf21c64b5f909352e260bda2e9ab057 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Tue, 7 Dec 2021 17:10:27 -0700 Subject: [PATCH 36/73] Search for attribute text --- .../Library/PathExtensions.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 8ce054c38c..73a658186a 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -29,18 +29,19 @@ namespace Emby.Server.Implementations.Library } var openBracketIndex = str.IndexOf('['); - var equalsIndex = str.IndexOf('='); + var attributeIndex = str.IndexOf(attribute); var closingBracketIndex = str.IndexOf(']'); - while (openBracketIndex < equalsIndex && equalsIndex < closingBracketIndex) + while (openBracketIndex < attributeIndex && attributeIndex < closingBracketIndex) { - if (str[(openBracketIndex + 1)..equalsIndex].Equals(attribute, StringComparison.OrdinalIgnoreCase)) + if (openBracketIndex + 1 == attributeIndex + && str[attributeIndex + attribute.Length] == '=') { - return str[(equalsIndex + 1)..closingBracketIndex].Trim().ToString(); + return str[(attributeIndex + attribute.Length + 1)..closingBracketIndex].Trim().ToString(); } - str = str[(closingBracketIndex+ 1)..]; + str = str[(closingBracketIndex + 1)..]; openBracketIndex = str.IndexOf('['); - equalsIndex = str.IndexOf('='); + attributeIndex = str.IndexOf(attribute); closingBracketIndex = str.IndexOf(']'); } From 01a0a4a87ca6b89d5b235e5b240afb0abf44809d Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Wed, 8 Dec 2021 10:16:48 +0100 Subject: [PATCH 37/73] Add audioResolver argument to FFProbeVideoInfo initialization --- MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index fc59e410fd..19a435196a 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -74,7 +74,8 @@ namespace MediaBrowser.Providers.MediaInfo config, subtitleManager, chapterManager, - libraryManager); + libraryManager, + _audioResolver); _audioProber = new FFProbeAudioInfo(mediaSourceManager, mediaEncoder, itemRepo, libraryManager); } From d47811bdaf8bbfc74c8185ef0bff7490726828d9 Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Wed, 8 Dec 2021 10:17:25 +0100 Subject: [PATCH 38/73] Fix wrong ffmpeg map argument due to wrong calculation --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 91f6654bb7..92b345f126 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2007,7 +2007,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.AudioStream.IsExternal) { int externalAudioMapIndex = state.SubtitleStream != null && state.SubtitleStream.IsExternal ? 2 : 1; - int externalAudioStream = state.MediaSource.MediaStreams.FindIndex(i => i.Path == state.AudioStream.Path); + int externalAudioStream = state.MediaSource.MediaStreams.Where(i => i.Path == state.AudioStream.Path).ToList().IndexOf(state.AudioStream); args += string.Format( CultureInfo.InvariantCulture, From 4cdb590291086ee623ab8ed945f9707ade94777a Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Wed, 8 Dec 2021 10:18:09 +0100 Subject: [PATCH 39/73] Exclude .strm files when searching for external audio files --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index bec8ee34a6..b68b4c248c 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -131,7 +131,7 @@ namespace MediaBrowser.Providers.MediaInfo for (int i = 0; i < files.Count; i++) { string file = files[i]; - if (string.Equals(video.Path, file, StringComparison.OrdinalIgnoreCase) || !AudioFileParser.IsAudioFile(file, _namingOptions)) + if (string.Equals(video.Path, file, StringComparison.OrdinalIgnoreCase) || !AudioFileParser.IsAudioFile(file, _namingOptions) || Path.GetExtension(file).Equals(".strm", StringComparison.OrdinalIgnoreCase)) { continue; } From e18d966874cbdcab16ee99571d88147a9ac198fb Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Wed, 8 Dec 2021 16:49:20 +0100 Subject: [PATCH 40/73] Add "Async" suffix to AddExternalAudio method Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 542e56256e..07d87e6076 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -586,7 +586,7 @@ namespace MediaBrowser.Providers.MediaInfo /// The current streams. /// The refreshOptions. /// The cancellation token. - private async Task AddExternalAudio( + private async Task AddExternalAudioAsync( Video video, List currentStreams, MetadataRefreshOptions options, From 65833076dbc574cc830fdd370a3a6127cfdc3bcf Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Wed, 8 Dec 2021 16:49:27 +0100 Subject: [PATCH 41/73] Add "Async" suffix to AddExternalAudio method Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 07d87e6076..77372e0635 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -217,7 +217,7 @@ namespace MediaBrowser.Providers.MediaInfo await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); - await AddExternalAudio(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); + await AddExternalAudioAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); var libraryOptions = _libraryManager.GetLibraryOptions(video); From 03b3f08354f496d18444779977bfc1602bbb2c73 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Wed, 8 Dec 2021 18:55:28 +0100 Subject: [PATCH 42/73] Format code in MediaBrowser.Providers/MediaInfo/AudioResolver.cs Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index b68b4c248c..425913501a 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -131,7 +131,9 @@ namespace MediaBrowser.Providers.MediaInfo for (int i = 0; i < files.Count; i++) { string file = files[i]; - if (string.Equals(video.Path, file, StringComparison.OrdinalIgnoreCase) || !AudioFileParser.IsAudioFile(file, _namingOptions) || Path.GetExtension(file).Equals(".strm", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(video.Path, file, StringComparison.OrdinalIgnoreCase) + || !AudioFileParser.IsAudioFile(file, _namingOptions) + || Path.GetExtension(file.AsSpan()).Equals(".strm", StringComparison.OrdinalIgnoreCase)) { continue; } From ee80602a0d59f043b1901138e24241f74284cb65 Mon Sep 17 00:00:00 2001 From: Muhammed Aljailane Date: Tue, 7 Dec 2021 10:04:50 +0000 Subject: [PATCH 43/73] Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/ --- Emby.Server.Implementations/Localization/Core/ar.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index 570d600fc3..9d4d40e512 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -15,7 +15,7 @@ "Favorites": "مفضلات", "Folders": "المجلدات", "Genres": "التضنيفات", - "HeaderAlbumArtists": "ألبوم الفنان", + "HeaderAlbumArtists": "فناني الألبوم", "HeaderContinueWatching": "استمر بالمشاهدة", "HeaderFavoriteAlbums": "الألبومات المفضلة", "HeaderFavoriteArtists": "الفنانون المفضلون", From 8bd331f5fa07c0d6f4c394ed8adcf442587d6bae Mon Sep 17 00:00:00 2001 From: WWWesten Date: Tue, 7 Dec 2021 18:32:48 +0000 Subject: [PATCH 44/73] Translated using Weblate (Croatian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hr/ --- Emby.Server.Implementations/Localization/Core/hr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json index d7cda61da6..4df0444e6d 100644 --- a/Emby.Server.Implementations/Localization/Core/hr.json +++ b/Emby.Server.Implementations/Localization/Core/hr.json @@ -15,7 +15,7 @@ "Favorites": "Favoriti", "Folders": "Mape", "Genres": "Žanrovi", - "HeaderAlbumArtists": "Album od izvođača", + "HeaderAlbumArtists": "Izvođači albuma", "HeaderContinueWatching": "Nastavi gledati", "HeaderFavoriteAlbums": "Omiljeni albumi", "HeaderFavoriteArtists": "Omiljeni izvođači", From 8aef2cb282b0b1fab08590fe7b6c6a9ee79c09e0 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Tue, 7 Dec 2021 18:35:17 +0000 Subject: [PATCH 45/73] Translated using Weblate (Lithuanian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/lt/ --- Emby.Server.Implementations/Localization/Core/lt-LT.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json index f3a131d405..f0a07f604c 100644 --- a/Emby.Server.Implementations/Localization/Core/lt-LT.json +++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json @@ -1,7 +1,7 @@ { "Albums": "Albumai", "AppDeviceValues": "Programa: {0}, Įrenginys: {1}", - "Application": "Programa", + "Application": "Programėlė", "Artists": "Atlikėjai", "AuthenticationSucceededWithUserName": "{0} sėkmingai autentifikuota", "Books": "Knygos", From 6640f3f782de64b7e7017760522c086b5a1c9d99 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Mon, 6 Dec 2021 08:49:27 +0000 Subject: [PATCH 46/73] Translated using Weblate (Malay) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/ --- Emby.Server.Implementations/Localization/Core/ms.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index 94ee389d7c..f5131c7836 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -55,7 +55,7 @@ "NotificationOptionPluginInstalled": "Plugin telah dipasang", "NotificationOptionPluginUninstalled": "Plugin telah dinyahpasang", "NotificationOptionPluginUpdateInstalled": "Kemaskini plugin telah dipasang", - "NotificationOptionServerRestartRequired": "Server restart required", + "NotificationOptionServerRestartRequired": "", "NotificationOptionTaskFailed": "Kegagalan tugas berjadual", "NotificationOptionUserLockedOut": "Pengguna telah dikunci", "NotificationOptionVideoPlayback": "Ulangmain video bermula", @@ -75,7 +75,7 @@ "StartupEmbyServerIsLoading": "Pelayan Jellyfin sedang dimuatkan. Sila cuba sebentar lagi.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "Muat turun sarikata gagal dari {0} untuk {1}", - "Sync": "Sync", + "Sync": "", "System": "Sistem", "TvShows": "Tayangan TV", "User": "User", From 1acb9e1d45a289d15ce72cff8f7bcbb5d9c2f314 Mon Sep 17 00:00:00 2001 From: archon eleven Date: Mon, 6 Dec 2021 08:48:09 +0000 Subject: [PATCH 47/73] Translated using Weblate (Malay) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/ --- Emby.Server.Implementations/Localization/Core/ms.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index f5131c7836..2e0fbc366c 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -66,11 +66,11 @@ "PluginInstalledWithName": "{0} telah dipasang", "PluginUninstalledWithName": "{0} telah dinyahpasang", "PluginUpdatedWithName": "{0} telah dikemaskini", - "ProviderValue": "Provider: {0}", + "ProviderValue": "Pembekal: {0}", "ScheduledTaskFailedWithName": "{0} gagal", "ScheduledTaskStartedWithName": "{0} bermula", "ServerNameNeedsToBeRestarted": "{0} perlu di ulangmula", - "Shows": "Series", + "Shows": "Tayangan", "Songs": "Lagu-lagu", "StartupEmbyServerIsLoading": "Pelayan Jellyfin sedang dimuatkan. Sila cuba sebentar lagi.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", @@ -78,7 +78,7 @@ "Sync": "", "System": "Sistem", "TvShows": "Tayangan TV", - "User": "User", + "User": "Pengguna", "UserCreatedWithName": "Pengguna {0} telah diwujudkan", "UserDeletedWithName": "Pengguna {0} telah dipadamkan", "UserDownloadingItemWithValues": "{0} sedang memuat turun {1}", From f50b2f7959a584ec0292beaef869c550e2f45b2d Mon Sep 17 00:00:00 2001 From: Weevild Date: Mon, 6 Dec 2021 12:50:32 +0000 Subject: [PATCH 48/73] Translated using Weblate (Swedish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sv/ --- Emby.Server.Implementations/Localization/Core/sv.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json index 1cef30b6c5..f3f6016615 100644 --- a/Emby.Server.Implementations/Localization/Core/sv.json +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -96,8 +96,8 @@ "TaskDownloadMissingSubtitles": "Ladda ned saknade undertexter", "TaskRefreshChannelsDescription": "Uppdaterar information för internetkanaler.", "TaskRefreshChannels": "Uppdatera kanaler", - "TaskCleanTranscodeDescription": "Raderar transkodningsfiler som är mer än en dag gamla.", - "TaskCleanTranscode": "Töm transkodningskatalog", + "TaskCleanTranscodeDescription": "Raderar omkodningsfiler som är mer än en dag gamla.", + "TaskCleanTranscode": "Töm omkodningskatalog", "TaskUpdatePluginsDescription": "Laddar ned och installerar uppdateringar till insticksprogram som är konfigurerade att uppdateras automatiskt.", "TaskUpdatePlugins": "Uppdatera insticksprogram", "TaskRefreshPeopleDescription": "Uppdaterar metadata för skådespelare och regissörer i ditt mediabibliotek.", From 5c407dbab695ba9ef15299b781b288c873edb777 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Tue, 7 Dec 2021 18:36:09 +0000 Subject: [PATCH 49/73] Translated using Weblate (Romanian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ro/ --- Emby.Server.Implementations/Localization/Core/ro.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ro.json b/Emby.Server.Implementations/Localization/Core/ro.json index 510aac11c6..f8fad7b631 100644 --- a/Emby.Server.Implementations/Localization/Core/ro.json +++ b/Emby.Server.Implementations/Localization/Core/ro.json @@ -74,7 +74,7 @@ "HeaderFavoriteArtists": "Artiști Favoriți", "HeaderFavoriteAlbums": "Albume Favorite", "HeaderContinueWatching": "Vizionează în continuare", - "HeaderAlbumArtists": "Album Artiști", + "HeaderAlbumArtists": "Albume Artiști", "Genres": "Genuri", "Folders": "Dosare", "Favorites": "Favorite", From 13ac3e3665468bb43edb5e7b05f3059089ab8297 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Tue, 7 Dec 2021 18:42:08 +0000 Subject: [PATCH 50/73] Translated using Weblate (Macedonian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/mk/ --- Emby.Server.Implementations/Localization/Core/mk.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/mk.json b/Emby.Server.Implementations/Localization/Core/mk.json index d7839be57a..279734c5ee 100644 --- a/Emby.Server.Implementations/Localization/Core/mk.json +++ b/Emby.Server.Implementations/Localization/Core/mk.json @@ -97,5 +97,8 @@ "TasksChannelsCategory": "Интернет Канали", "TasksApplicationCategory": "Апликација", "TasksLibraryCategory": "Библиотека", - "TasksMaintenanceCategory": "Одржување" + "TasksMaintenanceCategory": "Одржување", + "Undefined": "Недефинирано", + "Forced": "Принудно", + "Default": "Зададено" } From 593b2fd359de378a2588a8d54d11ebc0bb2cd3f1 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Thu, 9 Dec 2021 08:06:06 -0700 Subject: [PATCH 51/73] Add more speed and more tests --- .../Library/PathExtensions.cs | 23 +++++++++++-------- .../Library/PathExtensionsTests.cs | 8 +++++++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 73a658186a..78850c149e 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -28,21 +28,26 @@ namespace Emby.Server.Implementations.Library throw new ArgumentException("String can't be empty.", nameof(attribute)); } - var openBracketIndex = str.IndexOf('['); var attributeIndex = str.IndexOf(attribute); - var closingBracketIndex = str.IndexOf(']'); - while (openBracketIndex < attributeIndex && attributeIndex < closingBracketIndex) + + // Must be at least 3 characters after the attribute =, ], any character. + var maxIndex = str.Length - attribute.Length - 3; + while (attributeIndex > -1 && attributeIndex < maxIndex) { - if (openBracketIndex + 1 == attributeIndex - && str[attributeIndex + attribute.Length] == '=') + var attributeEnd = attributeIndex + attribute.Length; + if (attributeIndex > 0 + && str[attributeIndex - 1] == '[' + && str[attributeEnd] == '=') { - return str[(attributeIndex + attribute.Length + 1)..closingBracketIndex].Trim().ToString(); + var closingIndex = str[attributeEnd..].IndexOf(']'); + if (closingIndex != -1) + { + return str[(attributeEnd + 1)..(attributeEnd + closingIndex)].Trim().ToString(); + } } - str = str[(closingBracketIndex + 1)..]; - openBracketIndex = str.IndexOf('['); + str = str[attributeEnd..]; attributeIndex = str.IndexOf(attribute); - closingBracketIndex = str.IndexOf(']'); } // for imdbid we also accept pattern matching diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs index d6cbe96474..950f0e76d5 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs @@ -14,6 +14,14 @@ namespace Jellyfin.Server.Implementations.Tests.Library [InlineData("Superman: Red Son [imdbid1=tt11111111][imdbid=tt10985510]", "imdbid", "tt10985510")] [InlineData("Superman: Red Son [tmdbid=618355][imdbid=tt10985510]", "imdbid", "tt10985510")] [InlineData("Superman: Red Son [tmdbid=618355][imdbid=tt10985510]", "tmdbid", "618355")] + [InlineData("[tmdbid=618355]", "tmdbid", "618355")] + [InlineData("tmdbid=618355][tmdbid=618355]", "tmdbid", "618355")] + [InlineData("[tmdbid=618355]tmdbid=618355]", "tmdbid", "618355")] + [InlineData("tmdbid=618355]", "tmdbid", null)] + [InlineData("[tmdbid=618355", "tmdbid", null)] + [InlineData("tmdbid=618355", "tmdbid", null)] + [InlineData("tmdbid=", "tmdbid", null)] + [InlineData("tmdbid", "tmdbid", null)] public void GetAttributeValue_ValidArgs_Correct(string input, string attribute, string? expectedResult) { Assert.Equal(expectedResult, PathExtensions.GetAttributeValue(input, attribute)); From de2d2921979c3e1cfc58cd256e1e5e70169925d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Dardenne?= Date: Thu, 9 Dec 2021 16:07:57 +0100 Subject: [PATCH 52/73] Added artist to '/' split whitelist --- MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 32ff1dee6b..1b3a4fa0f7 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -45,6 +45,7 @@ namespace MediaBrowser.MediaEncoding.Probing { "AC/DC", "Au/Ra", + "Bremer/McCoy", "이달의 소녀 1/3", "LOONA 1/3", "LOONA / yyxy", From eeb8192602627e912b491f3e7e6696ce415f3ce3 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Thu, 9 Dec 2021 08:38:00 -0700 Subject: [PATCH 53/73] Add StringComparison --- Emby.Server.Implementations/Library/PathExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 78850c149e..3fcbb4d65f 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentException("String can't be empty.", nameof(attribute)); } - var attributeIndex = str.IndexOf(attribute); + var attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase); // Must be at least 3 characters after the attribute =, ], any character. var maxIndex = str.Length - attribute.Length - 3; @@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.Library } str = str[attributeEnd..]; - attributeIndex = str.IndexOf(attribute); + attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase); } // for imdbid we also accept pattern matching From d707a201c9568df374579d6dde56d8068a070da0 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Thu, 9 Dec 2021 12:31:32 -0700 Subject: [PATCH 54/73] update tests --- .../Library/PathExtensionsTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs index 950f0e76d5..295a3c83e1 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs @@ -15,8 +15,8 @@ namespace Jellyfin.Server.Implementations.Tests.Library [InlineData("Superman: Red Son [tmdbid=618355][imdbid=tt10985510]", "imdbid", "tt10985510")] [InlineData("Superman: Red Son [tmdbid=618355][imdbid=tt10985510]", "tmdbid", "618355")] [InlineData("[tmdbid=618355]", "tmdbid", "618355")] - [InlineData("tmdbid=618355][tmdbid=618355]", "tmdbid", "618355")] - [InlineData("[tmdbid=618355]tmdbid=618355]", "tmdbid", "618355")] + [InlineData("tmdbid=111111][tmdbid=618355]", "tmdbid", "618355")] + [InlineData("[tmdbid=618355]tmdbid=111111]", "tmdbid", "618355")] [InlineData("tmdbid=618355]", "tmdbid", null)] [InlineData("[tmdbid=618355", "tmdbid", null)] [InlineData("tmdbid=618355", "tmdbid", null)] From 4d446eb614584b27366d5994bd01552ed4108b70 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Thu, 9 Dec 2021 06:06:59 +0000 Subject: [PATCH 55/73] Translated using Weblate (Esperanto) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/eo/ --- Emby.Server.Implementations/Localization/Core/eo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/eo.json b/Emby.Server.Implementations/Localization/Core/eo.json index 3cadde0a01..8abf7fa660 100644 --- a/Emby.Server.Implementations/Localization/Core/eo.json +++ b/Emby.Server.Implementations/Localization/Core/eo.json @@ -104,7 +104,7 @@ "TaskRefreshChannelsDescription": "Refreŝigas informon pri interretaj kanaloj.", "TaskDownloadMissingSubtitles": "Elŝuti mankantajn subtekstojn", "TaskCleanTranscode": "Malplenigi Transkodadan Katalogon", - "TaskRefreshChapterImages": "Eltiri Ĉapitraj Bildojn", + "TaskRefreshChapterImages": "Eltiri Ĉapitrajn Bildojn", "TaskCleanCache": "Malplenigi Staplan Katalogon", "TaskCleanActivityLog": "Malplenigi Aktivecan Ĵurnalon", "PluginUpdatedWithName": "{0} estis ĝisdatigita", From 3797879d05ae3c1d17462593c2f3b04f94eccfae Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sat, 11 Dec 2021 14:50:55 -0500 Subject: [PATCH 56/73] Automatically bump stable versions in build Should prevent strangeness with building these for pre-releases or actual release versions. Whatever the Git tag is, becomes the package version. --- .ci/azure-pipelines-package.yml | 20 ++++++++++++++++++++ bump_version | 18 ++++++++++-------- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml index 81693452f3..89f7137fd0 100644 --- a/.ci/azure-pipelines-package.yml +++ b/.ci/azure-pipelines-package.yml @@ -39,6 +39,14 @@ jobs: vmImage: 'ubuntu-latest' steps: + - script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )" + displayName: Set release version (stable) + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') + + - script: './bump-version $(JellyfinVersion)' + displayName: Bump internal version (stable) + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') + - script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) deployment' displayName: 'Build Dockerfile' @@ -80,6 +88,14 @@ jobs: vmImage: 'ubuntu-latest' steps: + - script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )" + displayName: Set release version (stable) + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') + + - script: './bump-version $(JellyfinVersion)' + displayName: Bump internal version (stable) + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') + - task: DownloadPipelineArtifact@2 displayName: 'Download OpenAPI Spec' inputs: @@ -127,6 +143,10 @@ jobs: displayName: Set release version (stable) condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') + - script: './bump-version $(JellyfinVersion)' + displayName: Bump internal version (stable) + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') + - task: Docker@2 displayName: 'Push Unstable Image' condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') diff --git a/bump_version b/bump_version index f615606e2d..1fcf7197c4 100755 --- a/bump_version +++ b/bump_version @@ -52,7 +52,8 @@ echo $old_version # Set the build.yaml version to the specified new_version old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars -sed -i "s/${old_version_sed}/${new_version}/g" ${build_file} +new_version_sed="$( cut -f1 -d'-' <<<"${new_version}" )" +sed -i "s/${old_version_sed}/${new_version_sed}/g" ${build_file} # update nuget package version for subproject in ${jellyfin_subprojects[@]}; do @@ -64,26 +65,27 @@ for subproject in ${jellyfin_subprojects[@]}; do | sed -E 's/([0-9\.]+[-a-z0-9]*)<\/VersionPrefix>/\1/' )" echo old nuget version: $old_version + new_version_sed="$( cut -f1 -d'-' <<<"${new_version}" )" # Set the nuget version to the specified new_version - sed -i "s|${old_version}|${new_version}|g" ${subproject} + sed -i "s|${old_version}|${new_version_sed}|g" ${subproject} done if [[ ${new_version} == *"-"* ]]; then - new_version_deb="$( sed 's/-/~/g' <<<"${new_version}" )" + new_version_pkg="$( sed 's/-/~/g' <<<"${new_version}" )" else - new_version_deb="${new_version}-1" + new_version_pkg="${new_version}-1" fi # Update the metapackage equivs file debian_equivs_file="debian/metapackage/jellyfin" -sed -i "s/${old_version_sed}/${new_version}/g" ${debian_equivs_file} +sed -i "s/${old_version_sed}/${new_version_pkg}/g" ${debian_equivs_file} # Write out a temporary Debian changelog with our new stuff appended and some templated formatting debian_changelog_file="debian/changelog" debian_changelog_temp="$( mktemp )" # Create new temp file with our changelog -echo -e "jellyfin-server (${new_version_deb}) unstable; urgency=medium +echo -e "jellyfin-server (${new_version_pkg}) unstable; urgency=medium * New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v${new_version} @@ -104,7 +106,7 @@ pushd ${fedora_spec_temp_dir} # Split out the stuff before and after changelog csplit jellyfin.spec "/^%changelog/" # produces xx00 xx01 # Update the version in xx00 -sed -i "s/${old_version_sed}/${new_version_sed}/g" xx00 +sed -i "s/${old_version_sed}/${new_version_pkg}/g" xx00 # Remove the header from xx01 sed -i '/^%changelog/d' xx01 # Create new temp file with our changelog @@ -121,5 +123,5 @@ mv ${fedora_spec_temp} ${fedora_spec_file} rm -rf ${fedora_spec_temp_dir} # Stage the changed files for commit -git add ${shared_version_file} ${build_file} ${debian_equivs_file} ${debian_changelog_file} ${fedora_spec_file} +git add . git status From 3223ef0e49030ec5470cb73c85ba8f978946a49b Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sat, 11 Dec 2021 14:58:20 -0500 Subject: [PATCH 57/73] Properly handle the -1 tag Avoids breaking Fedora/CentOS while preserving Debian. --- bump_version | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bump_version b/bump_version index 1fcf7197c4..3c5cb7897b 100755 --- a/bump_version +++ b/bump_version @@ -73,8 +73,10 @@ done if [[ ${new_version} == *"-"* ]]; then new_version_pkg="$( sed 's/-/~/g' <<<"${new_version}" )" + new_version_deb_sup="" else - new_version_pkg="${new_version}-1" + new_version_pkg="${new_version}" + new_version_deb_sup="-1" fi # Update the metapackage equivs file @@ -85,7 +87,7 @@ sed -i "s/${old_version_sed}/${new_version_pkg}/g" ${debian_equivs_file} debian_changelog_file="debian/changelog" debian_changelog_temp="$( mktemp )" # Create new temp file with our changelog -echo -e "jellyfin-server (${new_version_pkg}) unstable; urgency=medium +echo -e "jellyfin-server (${new_version_pkg}${new_version_deb_sup}) unstable; urgency=medium * New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v${new_version} From 584cf47ef5faecf3c951bb0b767c404b94157c95 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sat, 11 Dec 2021 15:03:07 -0500 Subject: [PATCH 58/73] Show detailed git status --- bump_version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bump_version b/bump_version index 3c5cb7897b..41d27f5c8a 100755 --- a/bump_version +++ b/bump_version @@ -126,4 +126,4 @@ rm -rf ${fedora_spec_temp_dir} # Stage the changed files for commit git add . -git status +git status -v From 1f02ab83cb592be38aaa0c2e24ac6506c447cf4e Mon Sep 17 00:00:00 2001 From: WWWesten Date: Fri, 10 Dec 2021 20:39:57 +0000 Subject: [PATCH 59/73] Translated using Weblate (Persian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fa/ --- Emby.Server.Implementations/Localization/Core/fa.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json index 3d3b3533fc..6960ff007a 100644 --- a/Emby.Server.Implementations/Localization/Core/fa.json +++ b/Emby.Server.Implementations/Localization/Core/fa.json @@ -118,5 +118,6 @@ "Default": "پیشفرض", "TaskCleanActivityLogDescription": "ورودی‌های قدیمی‌تر از سن تنظیم شده در سیاهه فعالیت را حذف می‌کند.", "TaskCleanActivityLog": "پاکسازی سیاهه فعالیت", - "Undefined": "تعریف نشده" + "Undefined": "تعریف نشده", + "TaskOptimizeDatabase": "بهینه سازی پایگاه داده" } From 5e8f27d473af677b2669ca29a22357a0ec712fb9 Mon Sep 17 00:00:00 2001 From: INOUE Daisuke Date: Fri, 10 Dec 2021 13:47:38 +0000 Subject: [PATCH 60/73] Translated using Weblate (Japanese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ja/ --- Emby.Server.Implementations/Localization/Core/ja.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json index 7f41561ecf..2588f1e8c0 100644 --- a/Emby.Server.Implementations/Localization/Core/ja.json +++ b/Emby.Server.Implementations/Localization/Core/ja.json @@ -16,7 +16,7 @@ "Folders": "フォルダー", "Genres": "ジャンル", "HeaderAlbumArtists": "アルバムアーティスト", - "HeaderContinueWatching": "視聴を続ける", + "HeaderContinueWatching": "続きを見る", "HeaderFavoriteAlbums": "お気に入りのアルバム", "HeaderFavoriteArtists": "お気に入りのアーティスト", "HeaderFavoriteEpisodes": "お気に入りのエピソード", From a90614d194314f8a4d6f097637836610ce8b6bbe Mon Sep 17 00:00:00 2001 From: WWWesten Date: Fri, 10 Dec 2021 17:45:25 +0000 Subject: [PATCH 61/73] Translated using Weblate (Zulu) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zu/ --- .../Localization/Core/zu.json | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/zu.json b/Emby.Server.Implementations/Localization/Core/zu.json index 0967ef424b..b5f4b920f3 100644 --- a/Emby.Server.Implementations/Localization/Core/zu.json +++ b/Emby.Server.Implementations/Localization/Core/zu.json @@ -1 +1,29 @@ -{} +{ + "TasksApplicationCategory": "Ukusetshenziswa", + "TasksLibraryCategory": "Umtapo", + "TasksMaintenanceCategory": "Ukunakekela", + "User": "Umsebenzisi", + "Undefined": "Akuchaziwe", + "System": "Isistimu", + "Sync": "Vumelanisa", + "Songs": "Amaculo", + "Shows": "Izinhlelo", + "Plugin": "Isijobelelo", + "Playlists": "Izinhla Zokudlalayo", + "Photos": "Izithombe", + "Music": "Umculo", + "Movies": "Amamuvi", + "Latest": "lwakamuva", + "Inherit": "Ngefa", + "Forced": "Kuphoqiwe", + "Application": "Ukusetshenziswa", + "Genres": "Izinhlobo", + "Folders": "Izikhwama", + "Favorites": "Izintandokazi", + "Default": "Okumisiwe", + "Collections": "Amaqoqo", + "Channels": "Amashaneli", + "Books": "Izincwadi", + "Artists": "Abadlali", + "Albums": "Ama-albhamu" +} From 510f92f4c5b8a4ac969c0761bf54dbd742b05a68 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 12 Dec 2021 01:26:47 +0100 Subject: [PATCH 62/73] Don't check floats for equality --- MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index bf6146e2b1..770881149f 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -45,7 +45,7 @@ namespace MediaBrowser.MediaEncoding.Probing { "AC/DC", "Au/Ra", - "Bremer/McCoy", + "Bremer/McCoy", "이달의 소녀 1/3", "LOONA 1/3", "LOONA / yyxy", @@ -723,8 +723,8 @@ namespace MediaBrowser.MediaEncoding.Probing // Some interlaced H.264 files in mp4 containers using MBAFF coding aren't flagged as being interlaced by FFprobe, // so for H.264 files we also calculate the frame rate from the codec time base and check if it is double the reported // frame rate (both rounded to the nearest integer) to determine if the file is interlaced - float roundedTimeBaseFPS = MathF.Round(1 / GetFrameRate(stream.CodecTimeBase) ?? 0); - float roundedDoubleFrameRate = MathF.Round(stream.AverageFrameRate * 2 ?? 0); + int roundedTimeBaseFPS = Convert.ToInt32(1 / GetFrameRate(stream.CodecTimeBase) ?? 0); + int roundedDoubleFrameRate = Convert.ToInt32(stream.AverageFrameRate * 2 ?? 0); bool videoInterlaced = !string.IsNullOrWhiteSpace(streamInfo.FieldOrder) && !string.Equals(streamInfo.FieldOrder, "progressive", StringComparison.OrdinalIgnoreCase); From 7ee96a59d3b69e15aec1df33ad46f47021f8a20b Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 12 Dec 2021 02:07:35 +0100 Subject: [PATCH 63/73] Use correct jpeg MIME type image/jpg isn't a valid MIME type --- MediaBrowser.Model/Net/MimeTypes.cs | 4 ++-- tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs | 7 +------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs index 506e8e9d63..3b03466e9d 100644 --- a/MediaBrowser.Model/Net/MimeTypes.cs +++ b/MediaBrowser.Model/Net/MimeTypes.cs @@ -116,7 +116,7 @@ namespace MediaBrowser.Model.Net { "audio/x-wavpack", ".wv" }, // Type image - { "image/jpg", ".jpg" }, + { "image/jpeg", ".jpg" }, { "image/x-png", ".png" }, // Type text @@ -137,7 +137,7 @@ namespace MediaBrowser.Model.Net /// The filename to find the MIME type of. /// The default value to return if no fitting MIME type is found. /// The correct MIME type for the given filename, or if it wasn't found. - [return: NotNullIfNotNullAttribute("defaultValue")] + [return: NotNullIfNotNull("defaultValue")] public static string? GetMimeType(string filename, string? defaultValue = null) { if (filename.Length == 0) diff --git a/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs b/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs index 55050cc954..cbab455f01 100644 --- a/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs +++ b/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs @@ -1,8 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using MediaBrowser.Model.Net; using Xunit; @@ -129,7 +124,7 @@ namespace Jellyfin.Model.Tests.Net [InlineData("font/woff2", ".woff2")] [InlineData("image/bmp", ".bmp")] [InlineData("image/gif", ".gif")] - [InlineData("image/jpg", ".jpg")] + [InlineData("image/jpeg", ".jpg")] [InlineData("image/png", ".png")] [InlineData("image/svg+xml", ".svg")] [InlineData("image/tiff", ".tif")] From 58f788e8abfce620db5a9b70165ff6cbe0931a46 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sat, 11 Dec 2021 19:43:01 -0700 Subject: [PATCH 64/73] Update tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs Co-authored-by: Bond-009 --- .../Library/PathExtensionsTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs index 295a3c83e1..54a63a5f2f 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs @@ -22,6 +22,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library [InlineData("tmdbid=618355", "tmdbid", null)] [InlineData("tmdbid=", "tmdbid", null)] [InlineData("tmdbid", "tmdbid", null)] + [InlineData("[tmdbid=][imdbid=tt10985510]", "tmdbid", null)] public void GetAttributeValue_ValidArgs_Correct(string input, string attribute, string? expectedResult) { Assert.Equal(expectedResult, PathExtensions.GetAttributeValue(input, attribute)); From a04f8f5efb3f067de2ce27e8804aab5c950ac284 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sat, 11 Dec 2021 20:35:43 -0700 Subject: [PATCH 65/73] Fix new test --- Emby.Server.Implementations/Library/PathExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 3fcbb4d65f..6f61dc7135 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -40,7 +40,8 @@ namespace Emby.Server.Implementations.Library && str[attributeEnd] == '=') { var closingIndex = str[attributeEnd..].IndexOf(']'); - if (closingIndex != -1) + // Must be at least 1 character before the closing bracket. + if (closingIndex > 1) { return str[(attributeEnd + 1)..(attributeEnd + closingIndex)].Trim().ToString(); } From 296a61cbc43d1c42c80ceef73bb92f3014f98f03 Mon Sep 17 00:00:00 2001 From: "Brian J. Murrell" Date: Sat, 11 Dec 2021 19:26:00 -0500 Subject: [PATCH 66/73] Run bump_version in make srpm Also add an "rpms" target that builds the RPMs using mock in a target environment. Signed-off-by: Brian J. Murrell --- fedora/Makefile | 59 +++++++++++++++++++++++++++----------------- fedora/jellyfin.spec | 2 +- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/fedora/Makefile b/fedora/Makefile index 97904ddd35..6b09458b54 100644 --- a/fedora/Makefile +++ b/fedora/Makefile @@ -1,26 +1,41 @@ VERSION := $(shell sed -ne '/^Version:/s/.* *//p' fedora/jellyfin.spec) +outdir ?= fedora/ +TARGET ?= fedora-35-x86_64 srpm: - cd fedora/; \ - SOURCE_DIR=.. \ - WORKDIR="$${PWD}"; \ - tar \ - --transform "s,^\.,jellyfin-server-$(VERSION)," \ - --exclude='.git*' \ - --exclude='**/.git' \ - --exclude='**/.hg' \ - --exclude='**/.vs' \ - --exclude='**/.vscode' \ - --exclude='deployment' \ - --exclude='**/bin' \ - --exclude='**/obj' \ - --exclude='**/.nuget' \ - --exclude='*.deb' \ - --exclude='*.rpm' \ - --exclude='jellyfin-server-$(VERSION).tar.gz' \ - -czf "jellyfin-server-$(VERSION).tar.gz" \ - -C $${SOURCE_DIR} ./ - cd fedora/; \ - rpmbuild -bs jellyfin.spec \ - --define "_sourcedir $$PWD/" \ + pushd fedora/; \ + if [ "$$(id -u)" = "0" ]; then \ + dnf -y install git; \ + fi; \ + version=$$(git describe --tags | sed -e 's/^v//' \ + -e 's/-[0-9]*-g.*$$//' \ + -e 's/-/~/'); \ + SOURCE_DIR=.. \ + WORKDIR="$${PWD}"; \ + tar \ + --transform "s,^\.,jellyfin-server-$$version," \ + --exclude='.git*' \ + --exclude='**/.git' \ + --exclude='**/.hg' \ + --exclude='**/.vs' \ + --exclude='**/.vscode' \ + --exclude=deployment \ + --exclude='**/bin' \ + --exclude='**/obj' \ + --exclude='**/.nuget' \ + --exclude='*.deb' \ + --exclude='*.rpm' \ + --exclude=jellyfin-server-$$version.tar.gz \ + -czf "jellyfin-server-$$version.tar.gz" \ + -C $${SOURCE_DIR} ./; \ + popd; \ + ./bump_version $$version + cd fedora/; \ + rpmbuild -bs jellyfin.spec \ + --define "_sourcedir $$PWD/" \ --define "_srcrpmdir $(outdir)" + +rpms: fedora/jellyfin-$(shell git describe --tags | sed -e 's/^v//' -e 's/-[0-9]*-g.*$$//' -e 's/-/~/')-1$(shell rpm --eval %dist).src.rpm + mock --addrepo=https://download.copr.fedorainfracloud.org/results/@dotnet-sig/dotnet-preview/$(TARGET)/ \ + --enable-network \ + -r $(TARGET) $< diff --git a/fedora/jellyfin.spec b/fedora/jellyfin.spec index 47dee7c13f..acc6100f63 100644 --- a/fedora/jellyfin.spec +++ b/fedora/jellyfin.spec @@ -12,7 +12,7 @@ Release: 1%{?dist} Summary: The Free Software Media System License: GPLv3 URL: https://jellyfin.org -# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%{version}.tar.gz` +# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%%{version}.tar.gz` Source0: jellyfin-server-%{version}.tar.gz Source11: jellyfin.service Source12: jellyfin.env From 32629cd7da0a39962009bffd9389a660c196f541 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sat, 11 Dec 2021 19:31:30 -0700 Subject: [PATCH 67/73] Use BaseItemKind where possible --- Emby.Dlna/ContentDirectory/ControlHandler.cs | 46 +++--- .../Channels/ChannelManager.cs | 2 +- .../Channels/ChannelPostScanTask.cs | 3 +- .../Data/SqliteItemRepository.cs | 139 ++++++++---------- Emby.Server.Implementations/Dto/DtoService.cs | 2 +- .../Images/CollectionFolderImageProvider.cs | 16 +- .../Images/DynamicImageProvider.cs | 3 +- .../Images/GenreImageProvider.cs | 2 +- .../Images/MusicGenreImageProvider.cs | 6 +- .../Library/LibraryManager.cs | 2 +- .../Library/MusicManager.cs | 4 +- .../Library/SearchEngine.cs | 46 +++--- .../Library/UserViewManager.cs | 16 +- .../Library/Validators/ArtistsValidator.cs | 3 +- .../Validators/CollectionPostScanTask.cs | 4 +- .../Library/Validators/PeopleValidator.cs | 3 +- .../Library/Validators/StudiosValidator.cs | 3 +- .../LiveTv/EmbyTV/EmbyTV.cs | 14 +- .../LiveTv/LiveTvDtoService.cs | 11 +- .../LiveTv/LiveTvManager.cs | 28 ++-- .../Playlists/PlaylistsFolder.cs | 3 +- .../Session/SessionManager.cs | 2 +- .../TV/TVSeriesManager.cs | 8 +- Jellyfin.Api/Controllers/ArtistsController.cs | 8 +- Jellyfin.Api/Controllers/FilterController.cs | 4 +- Jellyfin.Api/Controllers/GenresController.cs | 14 +- Jellyfin.Api/Controllers/ItemsController.cs | 12 +- Jellyfin.Api/Controllers/LibraryController.cs | 37 ++--- Jellyfin.Api/Controllers/MoviesController.cs | 26 ++-- .../Controllers/MusicGenresController.cs | 14 +- Jellyfin.Api/Controllers/SearchController.cs | 4 +- Jellyfin.Api/Controllers/SessionController.cs | 2 +- Jellyfin.Api/Controllers/StudiosController.cs | 4 +- .../Controllers/SuggestionsController.cs | 2 +- Jellyfin.Api/Controllers/TvShowsController.cs | 2 +- .../Controllers/UserLibraryController.cs | 2 +- Jellyfin.Api/Controllers/YearsController.cs | 4 +- Jellyfin.Api/Helpers/RequestHelpers.cs | 16 -- .../Entities/Audio/MusicArtist.cs | 2 +- .../Entities/Audio/MusicGenre.cs | 3 +- MediaBrowser.Controller/Entities/Folder.cs | 6 +- MediaBrowser.Controller/Entities/Genre.cs | 10 +- .../Entities/InternalItemsQuery.cs | 12 +- MediaBrowser.Controller/Entities/TV/Series.cs | 12 +- .../Entities/UserViewBuilder.cs | 40 ++--- MediaBrowser.Controller/Playlists/Playlist.cs | 4 +- .../Querying/LatestItemsQuery.cs | 3 +- MediaBrowser.Model/Search/SearchQuery.cs | 9 +- MediaBrowser.Model/Session/BrowseRequest.cs | 5 +- .../Manager/ProviderManager.cs | 3 +- .../MediaInfo/SubtitleScheduledTask.cs | 3 +- .../Helpers/RequestHelpersTests.cs | 30 ---- 52 files changed, 305 insertions(+), 354 deletions(-) diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 26a816107a..b354421eaa 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -539,7 +539,7 @@ namespace Emby.Dlna.ContentDirectory User = user, Recursive = true, IsMissing = false, - ExcludeItemTypes = new[] { nameof(Book) }, + ExcludeItemTypes = new[] { BaseItemKind.Book }, IsFolder = isFolder, MediaTypes = mediaTypes, DtoOptions = GetDtoOptions() @@ -619,7 +619,7 @@ namespace Emby.Dlna.ContentDirectory Limit = limit, StartIndex = startIndex, IsVirtualItem = false, - ExcludeItemTypes = new[] { nameof(Book) }, + ExcludeItemTypes = new[] { BaseItemKind.Book }, IsPlaceHolder = false, DtoOptions = GetDtoOptions(), OrderBy = GetOrderBy(sort, folder.IsPreSorted) @@ -644,7 +644,7 @@ namespace Emby.Dlna.ContentDirectory { StartIndex = startIndex, Limit = limit, - IncludeItemTypes = new[] { nameof(LiveTvChannel) }, + IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel }, OrderBy = GetOrderBy(sort, false) }; @@ -675,23 +675,23 @@ namespace Emby.Dlna.ContentDirectory switch (stubType) { case StubType.Latest: - return GetLatest(item, query, nameof(Audio)); + return GetLatest(item, query, BaseItemKind.Audio); case StubType.Playlists: return GetMusicPlaylists(query); case StubType.Albums: - return GetChildrenOfItem(item, query, nameof(MusicAlbum)); + return GetChildrenOfItem(item, query, BaseItemKind.MusicAlbum); case StubType.Artists: return GetMusicArtists(item, query); case StubType.AlbumArtists: return GetMusicAlbumArtists(item, query); case StubType.FavoriteAlbums: - return GetChildrenOfItem(item, query, nameof(MusicAlbum), true); + return GetChildrenOfItem(item, query, BaseItemKind.MusicAlbum, true); case StubType.FavoriteArtists: return GetFavoriteArtists(item, query); case StubType.FavoriteSongs: - return GetChildrenOfItem(item, query, nameof(Audio), true); + return GetChildrenOfItem(item, query, BaseItemKind.Audio, true); case StubType.Songs: - return GetChildrenOfItem(item, query, nameof(Audio)); + return GetChildrenOfItem(item, query, BaseItemKind.Audio); case StubType.Genres: return GetMusicGenres(item, query); } @@ -746,13 +746,13 @@ namespace Emby.Dlna.ContentDirectory case StubType.ContinueWatching: return GetMovieContinueWatching(item, query); case StubType.Latest: - return GetLatest(item, query, nameof(Movie)); + return GetLatest(item, query, BaseItemKind.Movie); case StubType.Movies: - return GetChildrenOfItem(item, query, nameof(Movie)); + return GetChildrenOfItem(item, query, BaseItemKind.Movie); case StubType.Collections: return GetMovieCollections(query); case StubType.Favorites: - return GetChildrenOfItem(item, query, nameof(Movie), true); + return GetChildrenOfItem(item, query, BaseItemKind.Movie, true); case StubType.Genres: return GetGenres(item, query); } @@ -831,13 +831,13 @@ namespace Emby.Dlna.ContentDirectory case StubType.NextUp: return GetNextUp(item, query); case StubType.Latest: - return GetLatest(item, query, nameof(Episode)); + return GetLatest(item, query, BaseItemKind.Episode); case StubType.Series: - return GetChildrenOfItem(item, query, nameof(Series)); + return GetChildrenOfItem(item, query, BaseItemKind.Series); case StubType.FavoriteSeries: - return GetChildrenOfItem(item, query, nameof(Series), true); + return GetChildrenOfItem(item, query, BaseItemKind.Series, true); case StubType.FavoriteEpisodes: - return GetChildrenOfItem(item, query, nameof(Episode), true); + return GetChildrenOfItem(item, query, BaseItemKind.Episode, true); case StubType.Genres: return GetGenres(item, query); } @@ -898,7 +898,7 @@ namespace Emby.Dlna.ContentDirectory private QueryResult GetMovieCollections(InternalItemsQuery query) { query.Recursive = true; - query.IncludeItemTypes = new[] { nameof(BoxSet) }; + query.IncludeItemTypes = new[] { BaseItemKind.BoxSet }; var result = _libraryManager.GetItemsResult(query); @@ -913,7 +913,7 @@ namespace Emby.Dlna.ContentDirectory /// The item type. /// A value indicating whether to only fetch favorite items. /// The . - private QueryResult GetChildrenOfItem(BaseItem parent, InternalItemsQuery query, string itemType, bool isFavorite = false) + private QueryResult GetChildrenOfItem(BaseItem parent, InternalItemsQuery query, BaseItemKind itemType, bool isFavorite = false) { query.Recursive = true; query.Parent = parent; @@ -1013,7 +1013,7 @@ namespace Emby.Dlna.ContentDirectory private QueryResult GetMusicPlaylists(InternalItemsQuery query) { query.Parent = null; - query.IncludeItemTypes = new[] { nameof(Playlist) }; + query.IncludeItemTypes = new[] { BaseItemKind.Playlist }; query.Recursive = true; var result = _libraryManager.GetItemsResult(query); @@ -1052,7 +1052,7 @@ namespace Emby.Dlna.ContentDirectory /// The . /// The item type. /// The . - private QueryResult GetLatest(BaseItem parent, InternalItemsQuery query, string itemType) + private QueryResult GetLatest(BaseItem parent, InternalItemsQuery query, BaseItemKind itemType) { query.OrderBy = Array.Empty<(string, SortOrder)>(); @@ -1086,7 +1086,7 @@ namespace Emby.Dlna.ContentDirectory { Recursive = true, ArtistIds = new[] { item.Id }, - IncludeItemTypes = new[] { nameof(MusicAlbum) }, + IncludeItemTypes = new[] { BaseItemKind.MusicAlbum }, Limit = limit, StartIndex = startIndex, DtoOptions = GetDtoOptions(), @@ -1115,8 +1115,8 @@ namespace Emby.Dlna.ContentDirectory GenreIds = new[] { item.Id }, IncludeItemTypes = new[] { - nameof(Movie), - nameof(Series) + BaseItemKind.Movie, + BaseItemKind.Series }, Limit = limit, StartIndex = startIndex, @@ -1144,7 +1144,7 @@ namespace Emby.Dlna.ContentDirectory { Recursive = true, GenreIds = new[] { item.Id }, - IncludeItemTypes = new[] { nameof(MusicAlbum) }, + IncludeItemTypes = new[] { BaseItemKind.MusicAlbum }, Limit = limit, StartIndex = startIndex, DtoOptions = GetDtoOptions(), diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index f65eaec1c8..8c167824ee 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -541,7 +541,7 @@ namespace Emby.Server.Implementations.Channels return _libraryManager.GetItemIds( new InternalItemsQuery { - IncludeItemTypes = new[] { nameof(Channel) }, + IncludeItemTypes = new[] { BaseItemKind.Channel }, OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) } }).Select(i => GetChannelFeatures(i)).ToArray(); } diff --git a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs index 2391eed428..b358ba4d55 100644 --- a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs +++ b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -51,7 +52,7 @@ namespace Emby.Server.Implementations.Channels var uninstalledChannels = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { nameof(Channel) }, + IncludeItemTypes = new[] { BaseItemKind.Channel }, ExcludeItemIds = installedChannelIds.ToArray() }); diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 13f1df7c86..7731eb694f 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -196,57 +196,56 @@ namespace Emby.Server.Implementations.Data private static readonly string _mediaAttachmentInsertPrefix; - private static readonly HashSet _programTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet _programTypes = new() { - "Program", - "TvChannel", - "LiveTvProgram", - "LiveTvTvChannel" + BaseItemKind.Program, + BaseItemKind.TvChannel, + BaseItemKind.LiveTvProgram, + BaseItemKind.LiveTvChannel }; - private static readonly HashSet _programExcludeParentTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet _programExcludeParentTypes = new() { - "Series", - "Season", - "MusicAlbum", - "MusicArtist", - "PhotoAlbum" + BaseItemKind.Series, + BaseItemKind.Season, + BaseItemKind.MusicAlbum, + BaseItemKind.MusicArtist, + BaseItemKind.PhotoAlbum }; - private static readonly HashSet _serviceTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet _serviceTypes = new() { - "TvChannel", - "LiveTvTvChannel" + BaseItemKind.TvChannel, + BaseItemKind.LiveTvChannel }; - private static readonly HashSet _startDateTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet _startDateTypes = new() { - "Program", - "LiveTvProgram" + BaseItemKind.Program, + BaseItemKind.LiveTvProgram }; - private static readonly HashSet _seriesTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet _seriesTypes = new() { - "Book", - "AudioBook", - "Episode", - "Season" + BaseItemKind.Book, + BaseItemKind.AudioBook, + BaseItemKind.Episode, + BaseItemKind.Season }; - private static readonly HashSet _artistExcludeParentTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet _artistExcludeParentTypes = new() { - "Series", - "Season", - "PhotoAlbum" + BaseItemKind.Series, + BaseItemKind.Season, + BaseItemKind.PhotoAlbum }; - private static readonly HashSet _artistsTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet _artistsTypes = new() { - "Audio", - "MusicAlbum", - "MusicVideo", - "AudioBook", - "AudioPodcast" + BaseItemKind.Audio, + BaseItemKind.MusicAlbum, + BaseItemKind.MusicVideo, + BaseItemKind.AudioBook }; private static readonly Type[] _knownTypes = @@ -2212,7 +2211,7 @@ namespace Emby.Server.Implementations.Data private bool HasProgramAttributes(InternalItemsQuery query) { - if (_programExcludeParentTypes.Contains(query.ParentType)) + if (query.ParentType != null && _programExcludeParentTypes.Contains(query.ParentType.Value)) { return false; } @@ -2227,7 +2226,7 @@ namespace Emby.Server.Implementations.Data private bool HasServiceName(InternalItemsQuery query) { - if (_programExcludeParentTypes.Contains(query.ParentType)) + if (query.ParentType != null && _programExcludeParentTypes.Contains(query.ParentType.Value)) { return false; } @@ -2242,7 +2241,7 @@ namespace Emby.Server.Implementations.Data private bool HasStartDate(InternalItemsQuery query) { - if (_programExcludeParentTypes.Contains(query.ParentType)) + if (query.ParentType != null && _programExcludeParentTypes.Contains(query.ParentType.Value)) { return false; } @@ -2262,7 +2261,7 @@ namespace Emby.Server.Implementations.Data return true; } - return query.IncludeItemTypes.Contains("Episode", StringComparer.OrdinalIgnoreCase); + return query.IncludeItemTypes.Contains(BaseItemKind.Episode); } private bool HasTrailerTypes(InternalItemsQuery query) @@ -2272,12 +2271,12 @@ namespace Emby.Server.Implementations.Data return true; } - return query.IncludeItemTypes.Contains("Trailer", StringComparer.OrdinalIgnoreCase); + return query.IncludeItemTypes.Contains(BaseItemKind.Trailer); } private bool HasArtistFields(InternalItemsQuery query) { - if (_artistExcludeParentTypes.Contains(query.ParentType)) + if (query.ParentType != null && _artistExcludeParentTypes.Contains(query.ParentType.Value)) { return false; } @@ -2292,7 +2291,7 @@ namespace Emby.Server.Implementations.Data private bool HasSeriesFields(InternalItemsQuery query) { - if (string.Equals(query.ParentType, "PhotoAlbum", StringComparison.OrdinalIgnoreCase)) + if (query.ParentType == BaseItemKind.PhotoAlbum) { return false; } @@ -3487,8 +3486,8 @@ namespace Emby.Server.Implementations.Data if (query.IsMovie == true) { if (query.IncludeItemTypes.Length == 0 - || query.IncludeItemTypes.Contains(nameof(Movie)) - || query.IncludeItemTypes.Contains(nameof(Trailer))) + || query.IncludeItemTypes.Contains(BaseItemKind.Movie) + || query.IncludeItemTypes.Contains(BaseItemKind.Trailer)) { whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)"); } @@ -3563,15 +3562,15 @@ namespace Emby.Server.Implementations.Data statement?.TryBind("@IsFolder", query.IsFolder); } - var includeTypes = query.IncludeItemTypes.Select(MapIncludeItemTypes).Where(x => x != null).ToArray(); + var includeTypes = query.IncludeItemTypes; // Only specify excluded types if no included types are specified - if (includeTypes.Length == 0) + if (query.IncludeItemTypes.Length == 0) { - var excludeTypes = query.ExcludeItemTypes.Select(MapIncludeItemTypes).Where(x => x != null).ToArray(); + var excludeTypes = query.ExcludeItemTypes; if (excludeTypes.Length == 1) { whereClauses.Add("type<>@type"); - statement?.TryBind("@type", excludeTypes[0]); + statement?.TryBind("@type", excludeTypes[0].ToString()); } else if (excludeTypes.Length > 1) { @@ -3582,7 +3581,7 @@ namespace Emby.Server.Implementations.Data else if (includeTypes.Length == 1) { whereClauses.Add("type=@type"); - statement?.TryBind("@type", includeTypes[0]); + statement?.TryBind("@type", includeTypes[0].ToString()); } else if (includeTypes.Length > 1) { @@ -3911,7 +3910,7 @@ namespace Emby.Server.Implementations.Data if (query.IsPlayed.HasValue) { // We should probably figure this out for all folders, but for right now, this is the only place where we need it - if (query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], nameof(Series), StringComparison.OrdinalIgnoreCase)) + if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == BaseItemKind.Series) { if (query.IsPlayed.Value) { @@ -4761,27 +4760,27 @@ namespace Emby.Server.Implementations.Data { var list = new List(); - if (IsTypeInQuery(nameof(Person), query)) + if (IsTypeInQuery(BaseItemKind.Person, query)) { list.Add(typeof(Person).FullName); } - if (IsTypeInQuery(nameof(Genre), query)) + if (IsTypeInQuery(BaseItemKind.Genre, query)) { list.Add(typeof(Genre).FullName); } - if (IsTypeInQuery(nameof(MusicGenre), query)) + if (IsTypeInQuery(BaseItemKind.MusicGenre, query)) { list.Add(typeof(MusicGenre).FullName); } - if (IsTypeInQuery(nameof(MusicArtist), query)) + if (IsTypeInQuery(BaseItemKind.MusicArtist, query)) { list.Add(typeof(MusicArtist).FullName); } - if (IsTypeInQuery(nameof(Studio), query)) + if (IsTypeInQuery(BaseItemKind.Studio, query)) { list.Add(typeof(Studio).FullName); } @@ -4789,14 +4788,14 @@ namespace Emby.Server.Implementations.Data return list; } - private bool IsTypeInQuery(string type, InternalItemsQuery query) + private bool IsTypeInQuery(BaseItemKind type, InternalItemsQuery query) { - if (query.ExcludeItemTypes.Contains(type, StringComparer.OrdinalIgnoreCase)) + if (query.ExcludeItemTypes.Contains(type)) { return false; } - return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type, StringComparer.OrdinalIgnoreCase); + return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type); } private string GetCleanValue(string value) @@ -4836,12 +4835,12 @@ namespace Emby.Server.Implementations.Data return true; } - if (query.IncludeItemTypes.Contains(nameof(Episode), StringComparer.OrdinalIgnoreCase) - || query.IncludeItemTypes.Contains(nameof(Video), StringComparer.OrdinalIgnoreCase) - || query.IncludeItemTypes.Contains(nameof(Movie), StringComparer.OrdinalIgnoreCase) - || query.IncludeItemTypes.Contains(nameof(MusicVideo), StringComparer.OrdinalIgnoreCase) - || query.IncludeItemTypes.Contains(nameof(Series), StringComparer.OrdinalIgnoreCase) - || query.IncludeItemTypes.Contains(nameof(Season), StringComparer.OrdinalIgnoreCase)) + if (query.IncludeItemTypes.Contains(BaseItemKind.Episode) + || query.IncludeItemTypes.Contains(BaseItemKind.Video) + || query.IncludeItemTypes.Contains(BaseItemKind.Movie) + || query.IncludeItemTypes.Contains(BaseItemKind.MusicVideo) + || query.IncludeItemTypes.Contains(BaseItemKind.Series) + || query.IncludeItemTypes.Contains(BaseItemKind.Season)) { return true; } @@ -4890,22 +4889,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type return dict; } - private string MapIncludeItemTypes(string value) - { - if (_types.TryGetValue(value, out string result)) - { - return result; - } - - if (IsValidType(value)) - { - return value; - } - - Logger.LogWarning("Unknown item type: {ItemType}", value); - return null; - } - public void DeleteItem(Guid id) { if (id == Guid.Empty) @@ -5569,7 +5552,7 @@ AND Type = @InternalPersonType)"); return result; } - private static ItemCounts GetItemCounts(IReadOnlyList reader, int countStartColumn, string[] typesToCount) + private static ItemCounts GetItemCounts(IReadOnlyList reader, int countStartColumn, BaseItemKind[] typesToCount) { var counts = new ItemCounts(); diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index b91ff64087..a34bfdb750 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -470,7 +470,7 @@ namespace Emby.Server.Implementations.Dto { var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery { - IncludeItemTypes = new[] { nameof(MusicAlbum) }, + IncludeItemTypes = new[] { BaseItemKind.MusicAlbum }, Name = item.Album, Limit = 1 }); diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs index 0229fbae79..7e12ebb087 100644 --- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs @@ -28,35 +28,35 @@ namespace Emby.Server.Implementations.Images var view = (CollectionFolder)item; var viewType = view.CollectionType; - string[] includeItemTypes; + BaseItemKind[] includeItemTypes; if (string.Equals(viewType, CollectionType.Movies, StringComparison.Ordinal)) { - includeItemTypes = new string[] { "Movie" }; + includeItemTypes = new[] { BaseItemKind.Movie }; } else if (string.Equals(viewType, CollectionType.TvShows, StringComparison.Ordinal)) { - includeItemTypes = new string[] { "Series" }; + includeItemTypes = new[] { BaseItemKind.Series }; } else if (string.Equals(viewType, CollectionType.Music, StringComparison.Ordinal)) { - includeItemTypes = new string[] { "MusicAlbum" }; + includeItemTypes = new[] { BaseItemKind.MusicAlbum }; } else if (string.Equals(viewType, CollectionType.Books, StringComparison.Ordinal)) { - includeItemTypes = new string[] { "Book", "AudioBook" }; + includeItemTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook }; } else if (string.Equals(viewType, CollectionType.BoxSets, StringComparison.Ordinal)) { - includeItemTypes = new string[] { "BoxSet" }; + includeItemTypes = new[] { BaseItemKind.BoxSet }; } else if (string.Equals(viewType, CollectionType.HomeVideos, StringComparison.Ordinal) || string.Equals(viewType, CollectionType.Photos, StringComparison.Ordinal)) { - includeItemTypes = new string[] { "Video", "Photo" }; + includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Photo }; } else { - includeItemTypes = new string[] { "Video", "Audio", "Photo", "Movie", "Series" }; + includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Audio, BaseItemKind.Photo, BaseItemKind.Movie, BaseItemKind.Series }; } var recursive = !string.Equals(CollectionType.Playlists, viewType, StringComparison.OrdinalIgnoreCase); diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs index 900b3fd9c6..0c3fe33a38 100644 --- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; @@ -41,7 +42,7 @@ namespace Emby.Server.Implementations.Images User = view.UserId.HasValue ? _userManager.GetUserById(view.UserId.Value) : null, CollapseBoxSetItems = false, Recursive = recursive, - ExcludeItemTypes = new[] { "UserView", "CollectionFolder", "Person" }, + ExcludeItemTypes = new[] { BaseItemKind.UserView, BaseItemKind.CollectionFolder, BaseItemKind.Person }, DtoOptions = new DtoOptions(false) }); diff --git a/Emby.Server.Implementations/Images/GenreImageProvider.cs b/Emby.Server.Implementations/Images/GenreImageProvider.cs index 1f5090f7f5..f8eefad6b5 100644 --- a/Emby.Server.Implementations/Images/GenreImageProvider.cs +++ b/Emby.Server.Implementations/Images/GenreImageProvider.cs @@ -43,7 +43,7 @@ namespace Emby.Server.Implementations.Images return _libraryManager.GetItemList(new InternalItemsQuery { Genres = new[] { item.Name }, - IncludeItemTypes = new[] { nameof(Series), nameof(Movie) }, + IncludeItemTypes = new[] { BaseItemKind.Series, BaseItemKind.Movie }, OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) }, Limit = 4, Recursive = true, diff --git a/Emby.Server.Implementations/Images/MusicGenreImageProvider.cs b/Emby.Server.Implementations/Images/MusicGenreImageProvider.cs index baf1c90517..31f053f065 100644 --- a/Emby.Server.Implementations/Images/MusicGenreImageProvider.cs +++ b/Emby.Server.Implementations/Images/MusicGenreImageProvider.cs @@ -44,9 +44,9 @@ namespace Emby.Server.Implementations.Images Genres = new[] { item.Name }, IncludeItemTypes = new[] { - nameof(MusicAlbum), - nameof(MusicVideo), - nameof(Audio) + BaseItemKind.MusicAlbum, + BaseItemKind.MusicVideo, + BaseItemKind.Audio }, OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) }, Limit = 4, diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 778b6225e1..57f66dcc30 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -965,7 +965,7 @@ namespace Emby.Server.Implementations.Library { var existing = GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { nameof(MusicArtist) }, + IncludeItemTypes = new[] { BaseItemKind.MusicArtist }, Name = name, DtoOptions = options }).Cast() diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index e2f1fb0ade..d332135647 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Library var genres = item .GetRecursiveChildren(user, new InternalItemsQuery(user) { - IncludeItemTypes = new[] { nameof(Audio) }, + IncludeItemTypes = new[] { BaseItemKind.Audio }, DtoOptions = dtoOptions }) .Cast /// The include item types. - public string[] IncludeItemTypes { get; set; } + public BaseItemKind[] IncludeItemTypes { get; set; } /// /// Gets or sets a value indicating whether this instance is played. diff --git a/MediaBrowser.Model/Search/SearchQuery.cs b/MediaBrowser.Model/Search/SearchQuery.cs index aedfa4d363..1caed827f3 100644 --- a/MediaBrowser.Model/Search/SearchQuery.cs +++ b/MediaBrowser.Model/Search/SearchQuery.cs @@ -2,6 +2,7 @@ #pragma warning disable CS1591 using System; +using Jellyfin.Data.Enums; namespace MediaBrowser.Model.Search { @@ -16,8 +17,8 @@ namespace MediaBrowser.Model.Search IncludeStudios = true; MediaTypes = Array.Empty(); - IncludeItemTypes = Array.Empty(); - ExcludeItemTypes = Array.Empty(); + IncludeItemTypes = Array.Empty(); + ExcludeItemTypes = Array.Empty(); } /// @@ -56,9 +57,9 @@ namespace MediaBrowser.Model.Search public string[] MediaTypes { get; set; } - public string[] IncludeItemTypes { get; set; } + public BaseItemKind[] IncludeItemTypes { get; set; } - public string[] ExcludeItemTypes { get; set; } + public BaseItemKind[] ExcludeItemTypes { get; set; } public Guid? ParentId { get; set; } diff --git a/MediaBrowser.Model/Session/BrowseRequest.cs b/MediaBrowser.Model/Session/BrowseRequest.cs index 65afe5cf34..5ad7d783a1 100644 --- a/MediaBrowser.Model/Session/BrowseRequest.cs +++ b/MediaBrowser.Model/Session/BrowseRequest.cs @@ -1,3 +1,5 @@ +using Jellyfin.Data.Enums; + #nullable disable namespace MediaBrowser.Model.Session { @@ -8,10 +10,9 @@ namespace MediaBrowser.Model.Session { /// /// Gets or sets the item type. - /// Artist, Genre, Studio, Person, or any kind of BaseItem. /// /// The type of the item. - public string ItemType { get; set; } + public BaseItemKind ItemType { get; set; } /// /// Gets or sets the item id. diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index ca557f6d61..6e57533618 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -11,6 +11,7 @@ using System.Net.Http; using System.Net.Mime; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using Jellyfin.Data.Events; using MediaBrowser.Common.Net; using MediaBrowser.Common.Progress; @@ -1133,7 +1134,7 @@ namespace MediaBrowser.Providers.Manager var albums = _libraryManager .GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { nameof(MusicAlbum) }, + IncludeItemTypes = new[] { BaseItemKind.MusicAlbum }, ArtistIds = new[] { item.Id }, DtoOptions = new DtoOptions(false) { diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 1eacbf1e1b..cce71b0673 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; @@ -66,7 +67,7 @@ namespace MediaBrowser.Providers.MediaInfo { var options = GetOptions(); - var types = new[] { "Episode", "Movie" }; + var types = new[] { BaseItemKind.Episode, BaseItemKind.Movie }; var dict = new Dictionary(); diff --git a/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs b/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs index 4ba7e1d2ff..c4640bd226 100644 --- a/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs +++ b/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs @@ -55,35 +55,5 @@ namespace Jellyfin.Api.Tests.Helpers return data; } - - [Fact] - public static void GetItemTypeStrings_Empty_Empty() - { - Assert.Empty(RequestHelpers.GetItemTypeStrings(Array.Empty())); - } - - [Fact] - public static void GetItemTypeStrings_Valid_Success() - { - BaseItemKind[] input = - { - BaseItemKind.AggregateFolder, - BaseItemKind.Audio, - BaseItemKind.BasePluginFolder, - BaseItemKind.CollectionFolder - }; - - string[] expected = - { - "AggregateFolder", - "Audio", - "BasePluginFolder", - "CollectionFolder" - }; - - var res = RequestHelpers.GetItemTypeStrings(input); - - Assert.Equal(expected, res); - } } } From 0120d80b780095e416a8fd40b7b3b111ec86d981 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Dec 2021 12:00:51 +0000 Subject: [PATCH 68/73] Bump System.Linq.Async from 5.0.0 to 5.1.0 Bumps [System.Linq.Async](https://github.com/dotnet/reactive) from 5.0.0 to 5.1.0. - [Release notes](https://github.com/dotnet/reactive/releases) - [Commits](https://github.com/dotnet/reactive/compare/ixnet-v5.0.0...ixnet-v5.1.0) --- updated-dependencies: - dependency-name: System.Linq.Async dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .../Jellyfin.Server.Implementations.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 73ee694245..8b461e6af2 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -18,7 +18,7 @@ - + From 250332104b18bafee415ab1a69b2b355f9c18f6b Mon Sep 17 00:00:00 2001 From: Stoica Tedy Date: Tue, 14 Dec 2021 09:31:35 +0200 Subject: [PATCH 69/73] Fixed crash in MigrationRunner The crashed was caused by importing the migrationOptions even if the migrations.xml file is non existant. [Issue]: ~/.config/jellyfin/migrations.xml not found #6992 --- Jellyfin.Server/Migrations/MigrationRunner.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index a6886c64a6..825cf7338d 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -75,6 +75,10 @@ namespace Jellyfin.Server.Migrations var xmlSerializer = new MyXmlSerializer(); var migrationConfigPath = Path.Join(appPaths.ConfigurationDirectoryPath, MigrationsListStore.StoreKey.ToLowerInvariant() + ".xml"); + if (!File.Exists(migrationConfigPath)) + { + return; + } var migrationOptions = (MigrationOptions)xmlSerializer.DeserializeFromFile(typeof(MigrationOptions), migrationConfigPath)!; // We have to deserialize it manually since the configuration manager may overwrite it From 4e0380193147ca1bce0e3d618de469fc0f63d031 Mon Sep 17 00:00:00 2001 From: Stoica Tedy Date: Tue, 14 Dec 2021 09:57:20 +0200 Subject: [PATCH 70/73] Update Jellyfin.Server/Migrations/MigrationRunner.cs Co-authored-by: Claus Vium --- Jellyfin.Server/Migrations/MigrationRunner.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 825cf7338d..57e2804658 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -75,11 +75,9 @@ namespace Jellyfin.Server.Migrations var xmlSerializer = new MyXmlSerializer(); var migrationConfigPath = Path.Join(appPaths.ConfigurationDirectoryPath, MigrationsListStore.StoreKey.ToLowerInvariant() + ".xml"); - if (!File.Exists(migrationConfigPath)) - { - return; - } - var migrationOptions = (MigrationOptions)xmlSerializer.DeserializeFromFile(typeof(MigrationOptions), migrationConfigPath)!; + var migrationOptions = File.Exists(migrationConfigPath) + ? (MigrationOptions)xmlSerializer.DeserializeFromFile(typeof(MigrationOptions), migrationConfigPath)! + : new MigrationOptions(); // We have to deserialize it manually since the configuration manager may overwrite it var serverConfig = (ServerConfiguration)xmlSerializer.DeserializeFromFile(typeof(ServerConfiguration), appPaths.SystemConfigurationFilePath)!; From 0edf77994ac7f1890b22d828c54f91b8f4ca1486 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Tue, 14 Dec 2021 07:41:29 -0700 Subject: [PATCH 71/73] Cache BaseItemKind --- MediaBrowser.Controller/Entities/BaseItem.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index b1ac2fe8ee..ce37c79373 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -40,6 +40,8 @@ namespace MediaBrowser.Controller.Entities /// public abstract class BaseItem : IHasProviderIds, IHasLookupInfo, IEquatable { + private BaseItemKind? _baseItemKind; + /// /// The trailer folder name. /// @@ -2009,7 +2011,7 @@ namespace MediaBrowser.Controller.Entities public BaseItemKind GetBaseItemKind() { - return Enum.Parse(GetClientTypeName()); + return _baseItemKind ??= Enum.Parse(GetClientTypeName()); } /// From 0f4da9f635d18b48bd8d107021c83b2fdc945e73 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Tue, 14 Dec 2021 19:27:23 +0100 Subject: [PATCH 72/73] Fix crash on missing server config file --- Jellyfin.Server/Migrations/MigrationRunner.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 57e2804658..e9a45c140f 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -80,7 +80,10 @@ namespace Jellyfin.Server.Migrations : new MigrationOptions(); // We have to deserialize it manually since the configuration manager may overwrite it - var serverConfig = (ServerConfiguration)xmlSerializer.DeserializeFromFile(typeof(ServerConfiguration), appPaths.SystemConfigurationFilePath)!; + var serverConfig = File.Exists(appPaths.SystemConfigurationFilePath) + ? (ServerConfiguration)xmlSerializer.DeserializeFromFile(typeof(ServerConfiguration), appPaths.SystemConfigurationFilePath)! + : new ServerConfiguration(); + HandleStartupWizardCondition(migrations, migrationOptions, serverConfig.IsStartupWizardCompleted, logger); PerformMigrations(migrations, migrationOptions, options => xmlSerializer.SerializeToFile(options, migrationConfigPath), logger); } From 87439665d782a7a0f24d4015d53d584e66e40c2f Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Tue, 14 Dec 2021 15:10:40 -0700 Subject: [PATCH 73/73] Use array instead of HashSet --- .../Data/SqliteItemRepository.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 7731eb694f..48c3710cb5 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -196,7 +196,7 @@ namespace Emby.Server.Implementations.Data private static readonly string _mediaAttachmentInsertPrefix; - private static readonly HashSet _programTypes = new() + private static readonly BaseItemKind[] _programTypes = new[] { BaseItemKind.Program, BaseItemKind.TvChannel, @@ -204,7 +204,7 @@ namespace Emby.Server.Implementations.Data BaseItemKind.LiveTvChannel }; - private static readonly HashSet _programExcludeParentTypes = new() + private static readonly BaseItemKind[] _programExcludeParentTypes = new[] { BaseItemKind.Series, BaseItemKind.Season, @@ -213,19 +213,19 @@ namespace Emby.Server.Implementations.Data BaseItemKind.PhotoAlbum }; - private static readonly HashSet _serviceTypes = new() + private static readonly BaseItemKind[] _serviceTypes = new[] { BaseItemKind.TvChannel, BaseItemKind.LiveTvChannel }; - private static readonly HashSet _startDateTypes = new() + private static readonly BaseItemKind[] _startDateTypes = new[] { BaseItemKind.Program, BaseItemKind.LiveTvProgram }; - private static readonly HashSet _seriesTypes = new() + private static readonly BaseItemKind[] _seriesTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook, @@ -233,14 +233,14 @@ namespace Emby.Server.Implementations.Data BaseItemKind.Season }; - private static readonly HashSet _artistExcludeParentTypes = new() + private static readonly BaseItemKind[] _artistExcludeParentTypes = new[] { BaseItemKind.Series, BaseItemKind.Season, BaseItemKind.PhotoAlbum }; - private static readonly HashSet _artistsTypes = new() + private static readonly BaseItemKind[] _artistsTypes = new[] { BaseItemKind.Audio, BaseItemKind.MusicAlbum,