2
0
Эх сурвалжийг харах

Merge branch 'master' into tonemap-overlay

Claus Vium 3 жил өмнө
parent
commit
ae031fdd28
100 өөрчлөгдсөн 1576 нэмэгдсэн , 1391 устгасан
  1. 1 0
      CONTRIBUTORS.md
  2. 1 1
      Dockerfile
  3. 1 1
      Dockerfile.arm
  4. 1 1
      Dockerfile.arm64
  5. 39 29
      Emby.Dlna/DlnaManager.cs
  6. 0 1
      Emby.Server.Implementations/ApplicationHost.cs
  7. 1 1
      Emby.Server.Implementations/Channels/ChannelManager.cs
  8. 13 19
      Emby.Server.Implementations/Collections/CollectionManager.cs
  9. 3 2
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  10. 17 0
      Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
  11. 20 14
      Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
  12. 12 10
      Emby.Server.Implementations/Localization/Core/af.json
  13. 1 1
      Emby.Server.Implementations/Localization/Core/ca.json
  14. 1 1
      Emby.Server.Implementations/Localization/Core/cs.json
  15. 7 5
      Emby.Server.Implementations/Localization/Core/el.json
  16. 3 3
      Emby.Server.Implementations/Localization/Core/en-US.json
  17. 10 8
      Emby.Server.Implementations/Localization/Core/es-MX.json
  18. 1 1
      Emby.Server.Implementations/Localization/Core/es.json
  19. 1 1
      Emby.Server.Implementations/Localization/Core/hu.json
  20. 3 3
      Emby.Server.Implementations/Localization/Core/it.json
  21. 1 1
      Emby.Server.Implementations/Localization/Core/ja.json
  22. 1 1
      Emby.Server.Implementations/Localization/Core/kk.json
  23. 1 1
      Emby.Server.Implementations/Localization/Core/ko.json
  24. 1 1
      Emby.Server.Implementations/Localization/Core/ml.json
  25. 4 3
      Emby.Server.Implementations/Localization/Core/pl.json
  26. 1 0
      Emby.Server.Implementations/Localization/Core/pr.json
  27. 3 1
      Emby.Server.Implementations/Localization/Core/pt-BR.json
  28. 2 1
      Emby.Server.Implementations/Localization/Core/pt.json
  29. 1 1
      Emby.Server.Implementations/Localization/Core/ru.json
  30. 1 1
      Emby.Server.Implementations/Localization/Core/sk.json
  31. 5 3
      Emby.Server.Implementations/Localization/Core/sv.json
  32. 2 2
      Emby.Server.Implementations/Localization/Core/vi.json
  33. 5 5
      Emby.Server.Implementations/Localization/Core/zh-CN.json
  34. 6 3
      Emby.Server.Implementations/Localization/Core/zh-HK.json
  35. 4 2
      Emby.Server.Implementations/Localization/Core/zh-TW.json
  36. 27 36
      Emby.Server.Implementations/Localization/LocalizationManager.cs
  37. 5 0
      Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs
  38. 3 6
      Jellyfin.Api/Controllers/LiveTvController.cs
  39. 8 3
      Jellyfin.Api/Helpers/ProgressiveFileStream.cs
  40. 1 1
      Jellyfin.Api/Jellyfin.Api.csproj
  41. 16 1
      Jellyfin.Data/Enums/BaseItemKind.cs
  42. 4 4
      Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
  43. 6 6
      Jellyfin.Server/Jellyfin.Server.csproj
  44. 24 24
      MediaBrowser.Common/Net/IPHost.cs
  45. 2 2
      MediaBrowser.Common/Plugins/BasePluginOfT.cs
  46. 4 4
      MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
  47. 1 3
      MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
  48. 4 5
      MediaBrowser.Controller/Channels/ChannelItemResult.cs
  49. 1 1
      MediaBrowser.Controller/Collections/CollectionCreationOptions.cs
  50. 0 2
      MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs
  51. 3 5
      MediaBrowser.Controller/Collections/ICollectionManager.cs
  52. 0 2
      MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs
  53. 3 5
      MediaBrowser.Controller/Dlna/IDlnaManager.cs
  54. 21 22
      MediaBrowser.Controller/Entities/AggregateFolder.cs
  55. 17 17
      MediaBrowser.Controller/Entities/Audio/Audio.cs
  56. 1 1
      MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs
  57. 26 26
      MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
  58. 32 30
      MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
  59. 18 16
      MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
  60. 1 1
      MediaBrowser.Controller/Entities/AudioBook.cs
  61. 523 381
      MediaBrowser.Controller/Entities/BaseItem.cs
  62. 5 0
      MediaBrowser.Controller/Entities/BaseItemExtensions.cs
  63. 6 6
      MediaBrowser.Controller/Entities/BasePluginFolder.cs
  64. 20 20
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  65. 2 0
      MediaBrowser.Controller/Entities/Extensions.cs
  66. 6 5
      MediaBrowser.Controller/Entities/Folder.cs
  67. 1 1
      MediaBrowser.Controller/Entities/ICollectionFolder.cs
  68. 2 0
      MediaBrowser.Controller/Entities/IHasMediaSources.cs
  69. 1 1
      MediaBrowser.Controller/Entities/IHasShares.cs
  70. 3 0
      MediaBrowser.Controller/Entities/IHasTrailers.cs
  71. 79 79
      MediaBrowser.Controller/Entities/InternalItemsQuery.cs
  72. 25 25
      MediaBrowser.Controller/Entities/Movies/BoxSet.cs
  73. 22 20
      MediaBrowser.Controller/Entities/Person.cs
  74. 1 1
      MediaBrowser.Controller/Entities/PersonInfo.cs
  75. 24 24
      MediaBrowser.Controller/Entities/Photo.cs
  76. 18 16
      MediaBrowser.Controller/Entities/Studio.cs
  77. 68 68
      MediaBrowser.Controller/Entities/TV/Episode.cs
  78. 48 44
      MediaBrowser.Controller/Entities/TV/Season.cs
  79. 11 3
      MediaBrowser.Controller/Entities/TV/Series.cs
  80. 4 4
      MediaBrowser.Controller/Entities/Trailer.cs
  81. 7 7
      MediaBrowser.Controller/Entities/UserItemData.cs
  82. 21 21
      MediaBrowser.Controller/Entities/UserRootFolder.cs
  83. 119 119
      MediaBrowser.Controller/Entities/Video.cs
  84. 17 17
      MediaBrowser.Controller/Entities/Year.cs
  85. 0 3
      MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
  86. 0 6
      MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
  87. 16 16
      MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
  88. 0 1
      MediaBrowser.Controller/Persistence/IUserDataRepository.cs
  89. 39 39
      MediaBrowser.Controller/Playlists/Playlist.cs
  90. 1 1
      MediaBrowser.Controller/Providers/IDirectoryService.cs
  91. 10 10
      MediaBrowser.Controller/Resolvers/IItemResolver.cs
  92. 19 0
      MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
  93. 9 9
      MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs
  94. 1 3
      MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
  95. 32 75
      MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
  96. 1 10
      MediaBrowser.Model/Globalization/ILocalizationManager.cs
  97. 1 0
      MediaBrowser.Model/System/SystemInfo.cs
  98. 4 4
      MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
  99. 3 1
      MediaBrowser.Providers/Manager/ItemImageProvider.cs
  100. 5 0
      MediaBrowser.Providers/Manager/MetadataService.cs

+ 1 - 0
CONTRIBUTORS.md

@@ -212,4 +212,5 @@
  - [Tim Hobbs](https://github.com/timhobbs)
  - [Tim Hobbs](https://github.com/timhobbs)
  - [SvenVandenbrande](https://github.com/SvenVandenbrande)
  - [SvenVandenbrande](https://github.com/SvenVandenbrande)
  - [olsh](https://github.com/olsh)
  - [olsh](https://github.com/olsh)
+ - [lbenini](https://github.com/lbenini)
  - [gnuyent](https://github.com/gnuyent)
  - [gnuyent](https://github.com/gnuyent)

+ 1 - 1
Dockerfile

@@ -8,7 +8,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
  && npm ci --no-audit --unsafe-perm \
  && npm ci --no-audit --unsafe-perm \
  && mv dist /dist
  && mv dist /dist
 
 
-FROM debian:buster-slim as app
+FROM debian:bullseye-slim as app
 
 
 # https://askubuntu.com/questions/972516/debian-frontend-environment-variable
 # https://askubuntu.com/questions/972516/debian-frontend-environment-variable
 ARG DEBIAN_FRONTEND="noninteractive"
 ARG DEBIAN_FRONTEND="noninteractive"

+ 1 - 1
Dockerfile.arm

@@ -14,7 +14,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
  && mv dist /dist
  && mv dist /dist
 
 
 FROM multiarch/qemu-user-static:x86_64-arm as qemu
 FROM multiarch/qemu-user-static:x86_64-arm as qemu
-FROM arm32v7/debian:buster-slim as app
+FROM arm32v7/debian:bullseye-slim as app
 
 
 # https://askubuntu.com/questions/972516/debian-frontend-environment-variable
 # https://askubuntu.com/questions/972516/debian-frontend-environment-variable
 ARG DEBIAN_FRONTEND="noninteractive"
 ARG DEBIAN_FRONTEND="noninteractive"

+ 1 - 1
Dockerfile.arm64

@@ -14,7 +14,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
  && mv dist /dist
  && mv dist /dist
 
 
 FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
 FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
-FROM arm64v8/debian:buster-slim as app
+FROM arm64v8/debian:bullseye-slim as app
 
 
 # https://askubuntu.com/questions/972516/debian-frontend-environment-variable
 # https://askubuntu.com/questions/972516/debian-frontend-environment-variable
 ARG DEBIAN_FRONTEND="noninteractive"
 ARG DEBIAN_FRONTEND="noninteractive"

+ 39 - 29
Emby.Dlna/DlnaManager.cs

@@ -1,7 +1,4 @@
-#nullable disable
-
 #pragma warning disable CS1591
 #pragma warning disable CS1591
-
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
@@ -96,12 +93,14 @@ namespace Emby.Dlna
             }
             }
         }
         }
 
 
+        /// <inheritdoc />
         public DeviceProfile GetDefaultProfile()
         public DeviceProfile GetDefaultProfile()
         {
         {
             return new DefaultProfile();
             return new DefaultProfile();
         }
         }
 
 
-        public DeviceProfile GetProfile(DeviceIdentification deviceInfo)
+        /// <inheritdoc />
+        public DeviceProfile? GetProfile(DeviceIdentification deviceInfo)
         {
         {
             if (deviceInfo == null)
             if (deviceInfo == null)
             {
             {
@@ -111,13 +110,13 @@ namespace Emby.Dlna
             var profile = GetProfiles()
             var profile = GetProfiles()
                 .FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification));
                 .FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification));
 
 
-            if (profile != null)
+            if (profile == null)
             {
             {
-                _logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name);
+                LogUnmatchedProfile(deviceInfo);
             }
             }
             else
             else
             {
             {
-                LogUnmatchedProfile(deviceInfo);
+                _logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name);
             }
             }
 
 
             return profile;
             return profile;
@@ -187,7 +186,8 @@ namespace Emby.Dlna
             }
             }
         }
         }
 
 
-        public DeviceProfile GetProfile(IHeaderDictionary headers)
+        /// <inheritdoc />
+        public DeviceProfile? GetProfile(IHeaderDictionary headers)
         {
         {
             if (headers == null)
             if (headers == null)
             {
             {
@@ -195,15 +195,13 @@ namespace Emby.Dlna
             }
             }
 
 
             var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
             var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
-
-            if (profile != null)
+            if (profile == null)
             {
             {
-                _logger.LogDebug("Found matching device profile: {0}", profile.Name);
+                _logger.LogDebug("No matching device profile found. {@Headers}", headers);
             }
             }
             else
             else
             {
             {
-                var headerString = string.Join(", ", headers.Select(i => string.Format(CultureInfo.InvariantCulture, "{0}={1}", i.Key, i.Value)));
-                _logger.LogDebug("No matching device profile found. {0}", headerString);
+                _logger.LogDebug("Found matching device profile: {0}", profile.Name);
             }
             }
 
 
             return profile;
             return profile;
@@ -253,19 +251,19 @@ namespace Emby.Dlna
                 return xmlFies
                 return xmlFies
                     .Select(i => ParseProfileFile(i, type))
                     .Select(i => ParseProfileFile(i, type))
                     .Where(i => i != null)
                     .Where(i => i != null)
-                    .ToList();
+                    .ToList()!; // We just filtered out all the nulls
             }
             }
             catch (IOException)
             catch (IOException)
             {
             {
-                return new List<DeviceProfile>();
+                return Array.Empty<DeviceProfile>();
             }
             }
         }
         }
 
 
-        private DeviceProfile ParseProfileFile(string path, DeviceProfileType type)
+        private DeviceProfile? ParseProfileFile(string path, DeviceProfileType type)
         {
         {
             lock (_profiles)
             lock (_profiles)
             {
             {
-                if (_profiles.TryGetValue(path, out Tuple<InternalProfileInfo, DeviceProfile> profileTuple))
+                if (_profiles.TryGetValue(path, out Tuple<InternalProfileInfo, DeviceProfile>? profileTuple))
                 {
                 {
                     return profileTuple.Item2;
                     return profileTuple.Item2;
                 }
                 }
@@ -293,7 +291,8 @@ namespace Emby.Dlna
             }
             }
         }
         }
 
 
-        public DeviceProfile GetProfile(string id)
+        /// <inheritdoc />
+        public DeviceProfile? GetProfile(string id)
         {
         {
             if (string.IsNullOrEmpty(id))
             if (string.IsNullOrEmpty(id))
             {
             {
@@ -322,6 +321,7 @@ namespace Emby.Dlna
             }
             }
         }
         }
 
 
+        /// <inheritdoc />
         public IEnumerable<DeviceProfileInfo> GetProfileInfos()
         public IEnumerable<DeviceProfileInfo> GetProfileInfos()
         {
         {
             return GetProfileInfosInternal().Select(i => i.Info);
             return GetProfileInfosInternal().Select(i => i.Info);
@@ -329,17 +329,14 @@ namespace Emby.Dlna
 
 
         private InternalProfileInfo GetInternalProfileInfo(FileSystemMetadata file, DeviceProfileType type)
         private InternalProfileInfo GetInternalProfileInfo(FileSystemMetadata file, DeviceProfileType type)
         {
         {
-            return new InternalProfileInfo
-            {
-                Path = file.FullName,
-
-                Info = new DeviceProfileInfo
+            return new InternalProfileInfo(
+                new DeviceProfileInfo
                 {
                 {
                     Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture),
                     Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture),
                     Name = _fileSystem.GetFileNameWithoutExtension(file),
                     Name = _fileSystem.GetFileNameWithoutExtension(file),
                     Type = type
                     Type = type
-                }
-            };
+                },
+                file.FullName);
         }
         }
 
 
         private async Task ExtractSystemProfilesAsync()
         private async Task ExtractSystemProfilesAsync()
@@ -359,7 +356,8 @@ namespace Emby.Dlna
                     systemProfilesPath,
                     systemProfilesPath,
                     Path.GetFileName(name.AsSpan()).Slice(namespaceName.Length));
                     Path.GetFileName(name.AsSpan()).Slice(namespaceName.Length));
 
 
-                using (var stream = _assembly.GetManifestResourceStream(name))
+                // The stream should exist as we just got its name from GetManifestResourceNames
+                using (var stream = _assembly.GetManifestResourceStream(name)!)
                 {
                 {
                     var fileInfo = _fileSystem.GetFileInfo(path);
                     var fileInfo = _fileSystem.GetFileInfo(path);
 
 
@@ -380,6 +378,7 @@ namespace Emby.Dlna
             Directory.CreateDirectory(UserProfilesPath);
             Directory.CreateDirectory(UserProfilesPath);
         }
         }
 
 
+        /// <inheritdoc />
         public void DeleteProfile(string id)
         public void DeleteProfile(string id)
         {
         {
             var info = GetProfileInfosInternal().First(i => string.Equals(id, i.Info.Id, StringComparison.OrdinalIgnoreCase));
             var info = GetProfileInfosInternal().First(i => string.Equals(id, i.Info.Id, StringComparison.OrdinalIgnoreCase));
@@ -397,6 +396,7 @@ namespace Emby.Dlna
             }
             }
         }
         }
 
 
+        /// <inheritdoc />
         public void CreateProfile(DeviceProfile profile)
         public void CreateProfile(DeviceProfile profile)
         {
         {
             profile = ReserializeProfile(profile);
             profile = ReserializeProfile(profile);
@@ -412,6 +412,7 @@ namespace Emby.Dlna
             SaveProfile(profile, path, DeviceProfileType.User);
             SaveProfile(profile, path, DeviceProfileType.User);
         }
         }
 
 
+        /// <inheritdoc />
         public void UpdateProfile(DeviceProfile profile)
         public void UpdateProfile(DeviceProfile profile)
         {
         {
             profile = ReserializeProfile(profile);
             profile = ReserializeProfile(profile);
@@ -470,9 +471,11 @@ namespace Emby.Dlna
 
 
             var json = JsonSerializer.Serialize(profile, _jsonOptions);
             var json = JsonSerializer.Serialize(profile, _jsonOptions);
 
 
-            return JsonSerializer.Deserialize<DeviceProfile>(json, _jsonOptions);
+            // Output can't be null if the input isn't null
+            return JsonSerializer.Deserialize<DeviceProfile>(json, _jsonOptions)!;
         }
         }
 
 
+        /// <inheritdoc />
         public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
         public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
         {
         {
             var profile = GetDefaultProfile();
             var profile = GetDefaultProfile();
@@ -482,6 +485,7 @@ namespace Emby.Dlna
             return new DescriptionXmlBuilder(profile, serverUuId, serverAddress, _appHost.FriendlyName, serverId).GetXml();
             return new DescriptionXmlBuilder(profile, serverUuId, serverAddress, _appHost.FriendlyName, serverId).GetXml();
         }
         }
 
 
+        /// <inheritdoc />
         public ImageStream GetIcon(string filename)
         public ImageStream GetIcon(string filename)
         {
         {
             var format = filename.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
             var format = filename.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
@@ -499,9 +503,15 @@ namespace Emby.Dlna
 
 
         private class InternalProfileInfo
         private class InternalProfileInfo
         {
         {
-            internal DeviceProfileInfo Info { get; set; }
+            internal InternalProfileInfo(DeviceProfileInfo info, string path)
+            {
+                Info = info;
+                Path = path;
+            }
+
+            internal DeviceProfileInfo Info { get; }
 
 
-            internal string Path { get; set; }
+            internal string Path { get; }
         }
         }
     }
     }
 
 

+ 0 - 1
Emby.Server.Implementations/ApplicationHost.cs

@@ -1099,7 +1099,6 @@ namespace Emby.Server.Implementations
                 ServerName = FriendlyName,
                 ServerName = FriendlyName,
                 LocalAddress = GetSmartApiUrl(source),
                 LocalAddress = GetSmartApiUrl(source),
                 SupportsLibraryMonitor = true,
                 SupportsLibraryMonitor = true,
-                EncoderLocation = _mediaEncoder.EncoderLocation,
                 SystemArchitecture = RuntimeInformation.OSArchitecture,
                 SystemArchitecture = RuntimeInformation.OSArchitecture,
                 PackageName = _startupOptions.PackageName
                 PackageName = _startupOptions.PackageName
             };
             };

+ 1 - 1
Emby.Server.Implementations/Channels/ChannelManager.cs

@@ -880,7 +880,7 @@ namespace Emby.Server.Implementations.Channels
             }
             }
         }
         }
 
 
-        private async Task CacheResponse(object result, string path)
+        private async Task CacheResponse(ChannelItemResult result, string path)
         {
         {
             try
             try
             {
             {

+ 13 - 19
Emby.Server.Implementations/Collections/CollectionManager.cs

@@ -1,5 +1,3 @@
-#nullable disable
-
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
@@ -63,13 +61,13 @@ namespace Emby.Server.Implementations.Collections
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
+        public event EventHandler<CollectionCreatedEventArgs>? CollectionCreated;
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
+        public event EventHandler<CollectionModifiedEventArgs>? ItemsAddedToCollection;
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
+        public event EventHandler<CollectionModifiedEventArgs>? ItemsRemovedFromCollection;
 
 
         private IEnumerable<Folder> FindFolders(string path)
         private IEnumerable<Folder> FindFolders(string path)
         {
         {
@@ -80,7 +78,7 @@ namespace Emby.Server.Implementations.Collections
                 .Where(i => _fileSystem.AreEqual(path, i.Path) || _fileSystem.ContainsSubPath(i.Path, path));
                 .Where(i => _fileSystem.AreEqual(path, i.Path) || _fileSystem.ContainsSubPath(i.Path, path));
         }
         }
 
 
-        internal async Task<Folder> EnsureLibraryFolder(string path, bool createIfNeeded)
+        internal async Task<Folder?> EnsureLibraryFolder(string path, bool createIfNeeded)
         {
         {
             var existingFolder = FindFolders(path).FirstOrDefault();
             var existingFolder = FindFolders(path).FirstOrDefault();
             if (existingFolder != null)
             if (existingFolder != null)
@@ -114,7 +112,7 @@ namespace Emby.Server.Implementations.Collections
             return Path.Combine(_appPaths.DataPath, "collections");
             return Path.Combine(_appPaths.DataPath, "collections");
         }
         }
 
 
-        private Task<Folder> GetCollectionsFolder(bool createIfNeeded)
+        private Task<Folder?> GetCollectionsFolder(bool createIfNeeded)
         {
         {
             return EnsureLibraryFolder(GetCollectionsFolderPath(), createIfNeeded);
             return EnsureLibraryFolder(GetCollectionsFolderPath(), createIfNeeded);
         }
         }
@@ -203,8 +201,7 @@ namespace Emby.Server.Implementations.Collections
 
 
         private async Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
         private async Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
         {
         {
-            var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
-            if (collection == null)
+            if (_libraryManager.GetItemById(collectionId) is not BoxSet collection)
             {
             {
                 throw new ArgumentException("No collection exists with the supplied Id");
                 throw new ArgumentException("No collection exists with the supplied Id");
             }
             }
@@ -256,9 +253,7 @@ namespace Emby.Server.Implementations.Collections
         /// <inheritdoc />
         /// <inheritdoc />
         public async Task RemoveFromCollectionAsync(Guid collectionId, IEnumerable<Guid> itemIds)
         public async Task RemoveFromCollectionAsync(Guid collectionId, IEnumerable<Guid> itemIds)
         {
         {
-            var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
-
-            if (collection == null)
+            if (_libraryManager.GetItemById(collectionId) is not BoxSet collection)
             {
             {
                 throw new ArgumentException("No collection exists with the supplied Id");
                 throw new ArgumentException("No collection exists with the supplied Id");
             }
             }
@@ -312,11 +307,7 @@ namespace Emby.Server.Implementations.Collections
 
 
             foreach (var item in items)
             foreach (var item in items)
             {
             {
-                if (item is not ISupportsBoxSetGrouping)
-                {
-                    results[item.Id] = item;
-                }
-                else
+                if (item is ISupportsBoxSetGrouping)
                 {
                 {
                     var itemId = item.Id;
                     var itemId = item.Id;
 
 
@@ -340,6 +331,7 @@ namespace Emby.Server.Implementations.Collections
                     }
                     }
 
 
                     var alreadyInResults = false;
                     var alreadyInResults = false;
+
                     // this is kind of a performance hack because only Video has alternate versions that should be in a box set?
                     // this is kind of a performance hack because only Video has alternate versions that should be in a box set?
                     if (item is Video video)
                     if (item is Video video)
                     {
                     {
@@ -355,11 +347,13 @@ namespace Emby.Server.Implementations.Collections
                         }
                         }
                     }
                     }
 
 
-                    if (!alreadyInResults)
+                    if (alreadyInResults)
                     {
                     {
-                        results[itemId] = item;
+                        continue;
                     }
                     }
                 }
                 }
+
+                results[item.Id] = item;
             }
             }
 
 
             return results.Values;
             return results.Values;

+ 3 - 2
Emby.Server.Implementations/Emby.Server.Implementations.csproj

@@ -23,14 +23,15 @@
   </ItemGroup>
   </ItemGroup>
 
 
   <ItemGroup>
   <ItemGroup>
+    <PackageReference Include="DiscUtils.Udf" Version="0.16.4" />
     <PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" />
     <PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" />
     <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
     <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
     <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.8" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.9" />
     <PackageReference Include="Mono.Nat" Version="3.0.1" />
     <PackageReference Include="Mono.Nat" Version="3.0.1" />
-    <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.1.0" />
+    <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.0" />
     <PackageReference Include="sharpcompress" Version="0.28.3" />
     <PackageReference Include="sharpcompress" Version="0.28.3" />
     <PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
     <PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
     <PackageReference Include="DotNet.Glob" Version="3.1.2" />
     <PackageReference Include="DotNet.Glob" Version="3.1.2" />

+ 17 - 0
Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs

@@ -5,6 +5,7 @@
 using System;
 using System;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
+using DiscUtils.Udf;
 using Emby.Naming.Video;
 using Emby.Naming.Video;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
@@ -201,6 +202,22 @@ namespace Emby.Server.Implementations.Library.Resolvers
                 {
                 {
                     video.IsoType = IsoType.BluRay;
                     video.IsoType = IsoType.BluRay;
                 }
                 }
+                else
+                {
+                    // use disc-utils, both DVDs and BDs use UDF filesystem
+                    using (var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read))
+                    {
+                        UdfReader udfReader = new UdfReader(videoFileStream);
+                        if (udfReader.DirectoryExists("VIDEO_TS"))
+                        {
+                            video.IsoType = IsoType.Dvd;
+                        }
+                        else if (udfReader.DirectoryExists("BDMV"))
+                        {
+                            video.IsoType = IsoType.BluRay;
+                        }
+                    }
+                }
             }
             }
         }
         }
 
 

+ 20 - 14
Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs

@@ -13,7 +13,6 @@ using System.Threading.Tasks;
 using Jellyfin.Extensions;
 using Jellyfin.Extensions;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
-using MediaBrowser.Controller;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.LiveTv;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
@@ -44,22 +43,29 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 
 
         public async Task<Stream> GetListingsStream(TunerHostInfo info, CancellationToken cancellationToken)
         public async Task<Stream> GetListingsStream(TunerHostInfo info, CancellationToken cancellationToken)
         {
         {
-            if (info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+            if (info == null)
             {
             {
-                using var requestMessage = new HttpRequestMessage(HttpMethod.Get, info.Url);
-                if (!string.IsNullOrEmpty(info.UserAgent))
-                {
-                    requestMessage.Headers.UserAgent.TryParseAdd(info.UserAgent);
-                }
+                throw new ArgumentNullException(nameof(info));
+            }
+
+            if (!info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+            {
+                return File.OpenRead(info.Url);
+            }
 
 
-                var response = await _httpClientFactory.CreateClient(NamedClient.Default)
-                    .SendAsync(requestMessage, cancellationToken)
-                    .ConfigureAwait(false);
-                response.EnsureSuccessStatusCode();
-                return await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+            using var requestMessage = new HttpRequestMessage(HttpMethod.Get, info.Url);
+            if (!string.IsNullOrEmpty(info.UserAgent))
+            {
+                requestMessage.Headers.UserAgent.TryParseAdd(info.UserAgent);
             }
             }
 
 
-            return File.OpenRead(info.Url);
+            // Set HttpCompletionOption.ResponseHeadersRead to prevent timeouts on larger files
+            var response = await _httpClientFactory.CreateClient(NamedClient.Default)
+                .SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
+                .ConfigureAwait(false);
+            response.EnsureSuccessStatusCode();
+
+            return await response.Content.ReadAsStreamAsync(cancellationToken);
         }
         }
 
 
         private async Task<List<ChannelInfo>> GetChannelsAsync(TextReader reader, string channelIdPrefix, string tunerHostId)
         private async Task<List<ChannelInfo>> GetChannelsAsync(TextReader reader, string channelIdPrefix, string tunerHostId)
@@ -83,7 +89,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
                 if (trimmedLine.StartsWith(ExtInfPrefix, StringComparison.OrdinalIgnoreCase))
                 if (trimmedLine.StartsWith(ExtInfPrefix, StringComparison.OrdinalIgnoreCase))
                 {
                 {
                     extInf = trimmedLine.Substring(ExtInfPrefix.Length).Trim();
                     extInf = trimmedLine.Substring(ExtInfPrefix.Length).Trim();
-                    _logger.LogInformation("Found m3u channel: {0}", extInf);
                 }
                 }
                 else if (!string.IsNullOrWhiteSpace(extInf) && !trimmedLine.StartsWith('#'))
                 else if (!string.IsNullOrWhiteSpace(extInf) && !trimmedLine.StartsWith('#'))
                 {
                 {
@@ -99,6 +104,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 
 
                     channel.Path = trimmedLine;
                     channel.Path = trimmedLine;
                     channels.Add(channel);
                     channels.Add(channel);
+                    _logger.LogInformation("Parsed channel: {ChannelName}", channel.Name);
                     extInf = string.Empty;
                     extInf = string.Empty;
                 }
                 }
             }
             }

+ 12 - 10
Emby.Server.Implementations/Localization/Core/af.json

@@ -2,24 +2,24 @@
     "Artists": "Kunstenare",
     "Artists": "Kunstenare",
     "Channels": "Kanale",
     "Channels": "Kanale",
     "Folders": "Lêergidse",
     "Folders": "Lêergidse",
-    "Favorites": "Gunstellinge",
+    "Favorites": "Gunstelinge",
     "HeaderFavoriteShows": "Gunsteling Vertonings",
     "HeaderFavoriteShows": "Gunsteling Vertonings",
     "ValueSpecialEpisodeName": "Spesiale - {0}",
     "ValueSpecialEpisodeName": "Spesiale - {0}",
-    "HeaderAlbumArtists": "Album Kunstenaars",
+    "HeaderAlbumArtists": "Kunstenaars se Album",
     "Books": "Boeke",
     "Books": "Boeke",
     "HeaderNextUp": "Volgende",
     "HeaderNextUp": "Volgende",
     "Movies": "Flieks",
     "Movies": "Flieks",
     "Shows": "Televisie Reekse",
     "Shows": "Televisie Reekse",
     "HeaderContinueWatching": "Kyk Verder",
     "HeaderContinueWatching": "Kyk Verder",
     "HeaderFavoriteEpisodes": "Gunsteling Episodes",
     "HeaderFavoriteEpisodes": "Gunsteling Episodes",
-    "Photos": "Fotos",
+    "Photos": "Foto's",
     "Playlists": "Snitlyste",
     "Playlists": "Snitlyste",
     "HeaderFavoriteArtists": "Gunsteling Kunstenaars",
     "HeaderFavoriteArtists": "Gunsteling Kunstenaars",
     "HeaderFavoriteAlbums": "Gunsteling Albums",
     "HeaderFavoriteAlbums": "Gunsteling Albums",
     "Sync": "Sinkroniseer",
     "Sync": "Sinkroniseer",
     "HeaderFavoriteSongs": "Gunsteling Liedjies",
     "HeaderFavoriteSongs": "Gunsteling Liedjies",
     "Songs": "Liedjies",
     "Songs": "Liedjies",
-    "DeviceOnlineWithName": "{0} gekoppel is",
+    "DeviceOnlineWithName": "{0} is gekoppel",
     "DeviceOfflineWithName": "{0} is ontkoppel",
     "DeviceOfflineWithName": "{0} is ontkoppel",
     "Collections": "Versamelings",
     "Collections": "Versamelings",
     "Inherit": "Ontvang",
     "Inherit": "Ontvang",
@@ -71,7 +71,7 @@
     "NameSeasonUnknown": "Seisoen Onbekend",
     "NameSeasonUnknown": "Seisoen Onbekend",
     "NameSeasonNumber": "Seisoen {0}",
     "NameSeasonNumber": "Seisoen {0}",
     "NameInstallFailed": "{0} installering het misluk",
     "NameInstallFailed": "{0} installering het misluk",
-    "MusicVideos": "Musiek videos",
+    "MusicVideos": "Musiek Videos",
     "Music": "Musiek",
     "Music": "Musiek",
     "MixedContent": "Gemengde inhoud",
     "MixedContent": "Gemengde inhoud",
     "MessageServerConfigurationUpdated": "Bediener konfigurasie is opgedateer",
     "MessageServerConfigurationUpdated": "Bediener konfigurasie is opgedateer",
@@ -79,15 +79,15 @@
     "MessageApplicationUpdatedTo": "Jellyfin Bediener is opgedateer na {0}",
     "MessageApplicationUpdatedTo": "Jellyfin Bediener is opgedateer na {0}",
     "MessageApplicationUpdated": "Jellyfin Bediener is opgedateer",
     "MessageApplicationUpdated": "Jellyfin Bediener is opgedateer",
     "Latest": "Nuutste",
     "Latest": "Nuutste",
-    "LabelRunningTimeValue": "Lopende tyd: {0}",
+    "LabelRunningTimeValue": "Werktyd: {0}",
     "LabelIpAddressValue": "IP adres: {0}",
     "LabelIpAddressValue": "IP adres: {0}",
     "ItemRemovedWithName": "{0} is uit versameling verwyder",
     "ItemRemovedWithName": "{0} is uit versameling verwyder",
-    "ItemAddedWithName": "{0} is in die versameling",
-    "HomeVideos": "Tuis opnames",
+    "ItemAddedWithName": "{0} is by die versameling gevoeg",
+    "HomeVideos": "Tuis Videos",
     "HeaderRecordingGroups": "Groep Opnames",
     "HeaderRecordingGroups": "Groep Opnames",
     "Genres": "Genres",
     "Genres": "Genres",
     "FailedLoginAttemptWithUserName": "Mislukte aansluiting van {0}",
     "FailedLoginAttemptWithUserName": "Mislukte aansluiting van {0}",
-    "ChapterNameValue": "Hoofstuk",
+    "ChapterNameValue": "Hoofstuk {0}",
     "CameraImageUploadedFrom": "'n Nuwe kamera photo opgelaai van {0}",
     "CameraImageUploadedFrom": "'n Nuwe kamera photo opgelaai van {0}",
     "AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer",
     "AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer",
     "Albums": "Albums",
     "Albums": "Albums",
@@ -117,5 +117,7 @@
     "Forced": "Geforseer",
     "Forced": "Geforseer",
     "Default": "Oorspronklik",
     "Default": "Oorspronklik",
     "TaskCleanActivityLogDescription": "Verwyder aktiwiteitsaantekeninge ouer as die opgestelde ouderdom.",
     "TaskCleanActivityLogDescription": "Verwyder aktiwiteitsaantekeninge ouer as die opgestelde ouderdom.",
-    "TaskCleanActivityLog": "Maak Aktiwiteitsaantekeninge Skoon"
+    "TaskCleanActivityLog": "Maak Aktiwiteitsaantekeninge Skoon",
+    "TaskOptimizeDatabaseDescription": "Komprimeer databasis en verkort vrye ruimte. As hierdie taak uitgevoer word nadat die media versameling geskandeer is of ander veranderings aangebring is wat databasisaanpassings impliseer, kan dit die prestasie verbeter.",
+    "TaskOptimizeDatabase": "Optimaliseer databasis"
 }
 }

+ 1 - 1
Emby.Server.Implementations/Localization/Core/ca.json

@@ -5,7 +5,7 @@
     "Artists": "Artistes",
     "Artists": "Artistes",
     "AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament",
     "AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament",
     "Books": "Llibres",
     "Books": "Llibres",
-    "CameraImageUploadedFrom": "Una nova imatge de la càmera ha estat pujada des de {0}",
+    "CameraImageUploadedFrom": "S'ha pujat una nova imatge des de la camera desde {0}",
     "Channels": "Canals",
     "Channels": "Canals",
     "ChapterNameValue": "Capítol {0}",
     "ChapterNameValue": "Capítol {0}",
     "Collections": "Col·leccions",
     "Collections": "Col·leccions",

+ 1 - 1
Emby.Server.Implementations/Localization/Core/cs.json

@@ -15,7 +15,7 @@
     "Favorites": "Oblíbené",
     "Favorites": "Oblíbené",
     "Folders": "Složky",
     "Folders": "Složky",
     "Genres": "Žánry",
     "Genres": "Žánry",
-    "HeaderAlbumArtists": "Umělci alba",
+    "HeaderAlbumArtists": "Album umělce",
     "HeaderContinueWatching": "Pokračovat ve sledování",
     "HeaderContinueWatching": "Pokračovat ve sledování",
     "HeaderFavoriteAlbums": "Oblíbená alba",
     "HeaderFavoriteAlbums": "Oblíbená alba",
     "HeaderFavoriteArtists": "Oblíbení interpreti",
     "HeaderFavoriteArtists": "Oblíbení interpreti",

+ 7 - 5
Emby.Server.Implementations/Localization/Core/el.json

@@ -1,5 +1,5 @@
 {
 {
-    "Albums": "Άλμπουμς",
+    "Albums": "Άλμπουμ",
     "AppDeviceValues": "Εφαρμογή: {0}, Συσκευή: {1}",
     "AppDeviceValues": "Εφαρμογή: {0}, Συσκευή: {1}",
     "Application": "Εφαρμογή",
     "Application": "Εφαρμογή",
     "Artists": "Καλλιτέχνες",
     "Artists": "Καλλιτέχνες",
@@ -15,7 +15,7 @@
     "Favorites": "Αγαπημένα",
     "Favorites": "Αγαπημένα",
     "Folders": "Φάκελοι",
     "Folders": "Φάκελοι",
     "Genres": "Είδη",
     "Genres": "Είδη",
-    "HeaderAlbumArtists": "Καλλιτέχνες του Άλμπουμ",
+    "HeaderAlbumArtists": "Άλμπουμ Καλλιτέχνη",
     "HeaderContinueWatching": "Συνεχίστε την παρακολούθηση",
     "HeaderContinueWatching": "Συνεχίστε την παρακολούθηση",
     "HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ",
     "HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ",
     "HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες",
     "HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες",
@@ -39,7 +39,7 @@
     "MixedContent": "Ανάμεικτο Περιεχόμενο",
     "MixedContent": "Ανάμεικτο Περιεχόμενο",
     "Movies": "Ταινίες",
     "Movies": "Ταινίες",
     "Music": "Μουσική",
     "Music": "Μουσική",
-    "MusicVideos": "Μουσικά βίντεο",
+    "MusicVideos": "Μουσικά Βίντεο",
     "NameInstallFailed": "{0} η εγκατάσταση απέτυχε",
     "NameInstallFailed": "{0} η εγκατάσταση απέτυχε",
     "NameSeasonNumber": "Κύκλος {0}",
     "NameSeasonNumber": "Κύκλος {0}",
     "NameSeasonUnknown": "Άγνωστος Κύκλος",
     "NameSeasonUnknown": "Άγνωστος Κύκλος",
@@ -62,7 +62,7 @@
     "NotificationOptionVideoPlaybackStopped": "Η αναπαραγωγή βίντεο σταμάτησε",
     "NotificationOptionVideoPlaybackStopped": "Η αναπαραγωγή βίντεο σταμάτησε",
     "Photos": "Φωτογραφίες",
     "Photos": "Φωτογραφίες",
     "Playlists": "Λίστες αναπαραγωγής",
     "Playlists": "Λίστες αναπαραγωγής",
-    "Plugin": "Plugin",
+    "Plugin": "Πρόσθετο",
     "PluginInstalledWithName": "{0} εγκαταστήθηκε",
     "PluginInstalledWithName": "{0} εγκαταστήθηκε",
     "PluginUninstalledWithName": "{0} έχει απεγκατασταθεί",
     "PluginUninstalledWithName": "{0} έχει απεγκατασταθεί",
     "PluginUpdatedWithName": "{0} έχει αναβαθμιστεί",
     "PluginUpdatedWithName": "{0} έχει αναβαθμιστεί",
@@ -118,5 +118,7 @@
     "TaskCleanActivityLog": "Καθαρό Αρχείο Καταγραφής Δραστηριοτήτων",
     "TaskCleanActivityLog": "Καθαρό Αρχείο Καταγραφής Δραστηριοτήτων",
     "Undefined": "Απροσδιόριστο",
     "Undefined": "Απροσδιόριστο",
     "Forced": "Εξαναγκασμένο",
     "Forced": "Εξαναγκασμένο",
-    "Default": "Προεπιλογή"
+    "Default": "Προεπιλογή",
+    "TaskOptimizeDatabaseDescription": "Συμπιέζει τη βάση δεδομένων και δημιουργεί ελεύθερο χώρο. Η εκτέλεση αυτής της εργασίας μετά τη σάρωση της βιβλιοθήκης ή την πραγματοποίηση άλλων αλλαγών που συνεπάγονται τροποποιήσεις της βάσης δεδομένων μπορεί να βελτιώσει την απόδοση.",
+    "TaskOptimizeDatabase": "Βελτιστοποίηση βάσης δεδομένων"
 }
 }

+ 3 - 3
Emby.Server.Implementations/Localization/Core/en-US.json

@@ -17,7 +17,7 @@
     "Folders": "Folders",
     "Folders": "Folders",
     "Forced": "Forced",
     "Forced": "Forced",
     "Genres": "Genres",
     "Genres": "Genres",
-    "HeaderAlbumArtists": "Album Artists",
+    "HeaderAlbumArtists": "Artist's Album",
     "HeaderContinueWatching": "Continue Watching",
     "HeaderContinueWatching": "Continue Watching",
     "HeaderFavoriteAlbums": "Favorite Albums",
     "HeaderFavoriteAlbums": "Favorite Albums",
     "HeaderFavoriteArtists": "Favorite Artists",
     "HeaderFavoriteArtists": "Favorite Artists",
@@ -27,7 +27,7 @@
     "HeaderLiveTV": "Live TV",
     "HeaderLiveTV": "Live TV",
     "HeaderNextUp": "Next Up",
     "HeaderNextUp": "Next Up",
     "HeaderRecordingGroups": "Recording Groups",
     "HeaderRecordingGroups": "Recording Groups",
-    "HomeVideos": "Home videos",
+    "HomeVideos": "Home Videos",
     "Inherit": "Inherit",
     "Inherit": "Inherit",
     "ItemAddedWithName": "{0} was added to the library",
     "ItemAddedWithName": "{0} was added to the library",
     "ItemRemovedWithName": "{0} was removed from the library",
     "ItemRemovedWithName": "{0} was removed from the library",
@@ -41,7 +41,7 @@
     "MixedContent": "Mixed content",
     "MixedContent": "Mixed content",
     "Movies": "Movies",
     "Movies": "Movies",
     "Music": "Music",
     "Music": "Music",
-    "MusicVideos": "Music videos",
+    "MusicVideos": "Music Videos",
     "NameInstallFailed": "{0} installation failed",
     "NameInstallFailed": "{0} installation failed",
     "NameSeasonNumber": "Season {0}",
     "NameSeasonNumber": "Season {0}",
     "NameSeasonUnknown": "Season Unknown",
     "NameSeasonUnknown": "Season Unknown",

+ 10 - 8
Emby.Server.Implementations/Localization/Core/es-MX.json

@@ -15,7 +15,7 @@
     "Favorites": "Favoritos",
     "Favorites": "Favoritos",
     "Folders": "Carpetas",
     "Folders": "Carpetas",
     "Genres": "Géneros",
     "Genres": "Géneros",
-    "HeaderAlbumArtists": "Artistas del álbum",
+    "HeaderAlbumArtists": "Artistas del Álbum",
     "HeaderContinueWatching": "Continuar viendo",
     "HeaderContinueWatching": "Continuar viendo",
     "HeaderFavoriteAlbums": "Álbumes favoritos",
     "HeaderFavoriteAlbums": "Álbumes favoritos",
     "HeaderFavoriteArtists": "Artistas favoritos",
     "HeaderFavoriteArtists": "Artistas favoritos",
@@ -25,7 +25,7 @@
     "HeaderLiveTV": "TV en vivo",
     "HeaderLiveTV": "TV en vivo",
     "HeaderNextUp": "A continuación",
     "HeaderNextUp": "A continuación",
     "HeaderRecordingGroups": "Grupos de grabación",
     "HeaderRecordingGroups": "Grupos de grabación",
-    "HomeVideos": "Videos caseros",
+    "HomeVideos": "Videos Caseros",
     "Inherit": "Heredar",
     "Inherit": "Heredar",
     "ItemAddedWithName": "{0} fue agregado a la biblioteca",
     "ItemAddedWithName": "{0} fue agregado a la biblioteca",
     "ItemRemovedWithName": "{0} fue removido de la biblioteca",
     "ItemRemovedWithName": "{0} fue removido de la biblioteca",
@@ -39,7 +39,7 @@
     "MixedContent": "Contenido mezclado",
     "MixedContent": "Contenido mezclado",
     "Movies": "Películas",
     "Movies": "Películas",
     "Music": "Música",
     "Music": "Música",
-    "MusicVideos": "Videos musicales",
+    "MusicVideos": "Videos Musicales",
     "NameInstallFailed": "Falló la instalación de {0}",
     "NameInstallFailed": "Falló la instalación de {0}",
     "NameSeasonNumber": "Temporada {0}",
     "NameSeasonNumber": "Temporada {0}",
     "NameSeasonUnknown": "Temporada desconocida",
     "NameSeasonUnknown": "Temporada desconocida",
@@ -49,7 +49,7 @@
     "NotificationOptionAudioPlayback": "Reproducción de audio iniciada",
     "NotificationOptionAudioPlayback": "Reproducción de audio iniciada",
     "NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida",
     "NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida",
     "NotificationOptionCameraImageUploaded": "Imagen de la cámara subida",
     "NotificationOptionCameraImageUploaded": "Imagen de la cámara subida",
-    "NotificationOptionInstallationFailed": "Falla de instalación",
+    "NotificationOptionInstallationFailed": "Fallo en la instalación",
     "NotificationOptionNewLibraryContent": "Nuevo contenido agregado",
     "NotificationOptionNewLibraryContent": "Nuevo contenido agregado",
     "NotificationOptionPluginError": "Falla de complemento",
     "NotificationOptionPluginError": "Falla de complemento",
     "NotificationOptionPluginInstalled": "Complemento instalado",
     "NotificationOptionPluginInstalled": "Complemento instalado",
@@ -69,7 +69,7 @@
     "ProviderValue": "Proveedor: {0}",
     "ProviderValue": "Proveedor: {0}",
     "ScheduledTaskFailedWithName": "{0} falló",
     "ScheduledTaskFailedWithName": "{0} falló",
     "ScheduledTaskStartedWithName": "{0} iniciado",
     "ScheduledTaskStartedWithName": "{0} iniciado",
-    "ServerNameNeedsToBeRestarted": "{0} debe ser reiniciado",
+    "ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado",
     "Shows": "Programas",
     "Shows": "Programas",
     "Songs": "Canciones",
     "Songs": "Canciones",
     "StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo pronto.",
     "StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo pronto.",
@@ -94,9 +94,9 @@
     "VersionNumber": "Versión {0}",
     "VersionNumber": "Versión {0}",
     "TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.",
     "TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.",
     "TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes",
     "TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes",
-    "TaskRefreshChannelsDescription": "Actualiza la información de canales de Internet.",
+    "TaskRefreshChannelsDescription": "Actualiza la información de los canales de Internet.",
     "TaskRefreshChannels": "Actualizar canales",
     "TaskRefreshChannels": "Actualizar canales",
-    "TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día.",
+    "TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día de antigüedad.",
     "TaskCleanTranscode": "Limpiar directorio de transcodificado",
     "TaskCleanTranscode": "Limpiar directorio de transcodificado",
     "TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.",
     "TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.",
     "TaskUpdatePlugins": "Actualizar complementos",
     "TaskUpdatePlugins": "Actualizar complementos",
@@ -118,5 +118,7 @@
     "TaskCleanActivityLog": "Limpiar registro de actividades",
     "TaskCleanActivityLog": "Limpiar registro de actividades",
     "Undefined": "Sin definir",
     "Undefined": "Sin definir",
     "Forced": "Forzado",
     "Forced": "Forzado",
-    "Default": "Predeterminado"
+    "Default": "Predeterminado",
+    "TaskOptimizeDatabase": "Optimizar base de datos",
+    "TaskOptimizeDatabaseDescription": "Compacta la base de datos y trunca el espacio libre. Puede mejorar el rendimiento si se realiza esta tarea después de escanear la biblioteca o después de realizar otros cambios que impliquen modificar la base de datos."
 }
 }

+ 1 - 1
Emby.Server.Implementations/Localization/Core/es.json

@@ -15,7 +15,7 @@
     "Favorites": "Favoritos",
     "Favorites": "Favoritos",
     "Folders": "Carpetas",
     "Folders": "Carpetas",
     "Genres": "Géneros",
     "Genres": "Géneros",
-    "HeaderAlbumArtists": "Artistas del álbum",
+    "HeaderAlbumArtists": "Artista del álbum",
     "HeaderContinueWatching": "Continuar viendo",
     "HeaderContinueWatching": "Continuar viendo",
     "HeaderFavoriteAlbums": "Álbumes favoritos",
     "HeaderFavoriteAlbums": "Álbumes favoritos",
     "HeaderFavoriteArtists": "Artistas favoritos",
     "HeaderFavoriteArtists": "Artistas favoritos",

+ 1 - 1
Emby.Server.Implementations/Localization/Core/hu.json

@@ -15,7 +15,7 @@
     "Favorites": "Kedvencek",
     "Favorites": "Kedvencek",
     "Folders": "Könyvtárak",
     "Folders": "Könyvtárak",
     "Genres": "Műfajok",
     "Genres": "Műfajok",
-    "HeaderAlbumArtists": "Album előadók",
+    "HeaderAlbumArtists": "Előadó albumai",
     "HeaderContinueWatching": "Megtekintés folytatása",
     "HeaderContinueWatching": "Megtekintés folytatása",
     "HeaderFavoriteAlbums": "Kedvenc albumok",
     "HeaderFavoriteAlbums": "Kedvenc albumok",
     "HeaderFavoriteArtists": "Kedvenc előadók",
     "HeaderFavoriteArtists": "Kedvenc előadók",

+ 3 - 3
Emby.Server.Implementations/Localization/Core/it.json

@@ -15,7 +15,7 @@
     "Favorites": "Preferiti",
     "Favorites": "Preferiti",
     "Folders": "Cartelle",
     "Folders": "Cartelle",
     "Genres": "Generi",
     "Genres": "Generi",
-    "HeaderAlbumArtists": "Artisti degli Album",
+    "HeaderAlbumArtists": "Artisti dell'Album",
     "HeaderContinueWatching": "Continua a guardare",
     "HeaderContinueWatching": "Continua a guardare",
     "HeaderFavoriteAlbums": "Album Preferiti",
     "HeaderFavoriteAlbums": "Album Preferiti",
     "HeaderFavoriteArtists": "Artisti Preferiti",
     "HeaderFavoriteArtists": "Artisti Preferiti",
@@ -25,7 +25,7 @@
     "HeaderLiveTV": "Diretta TV",
     "HeaderLiveTV": "Diretta TV",
     "HeaderNextUp": "Prossimo",
     "HeaderNextUp": "Prossimo",
     "HeaderRecordingGroups": "Gruppi di Registrazione",
     "HeaderRecordingGroups": "Gruppi di Registrazione",
-    "HomeVideos": "Video personali",
+    "HomeVideos": "Video Personali",
     "Inherit": "Eredita",
     "Inherit": "Eredita",
     "ItemAddedWithName": "{0} è stato aggiunto alla libreria",
     "ItemAddedWithName": "{0} è stato aggiunto alla libreria",
     "ItemRemovedWithName": "{0} è stato rimosso dalla libreria",
     "ItemRemovedWithName": "{0} è stato rimosso dalla libreria",
@@ -39,7 +39,7 @@
     "MixedContent": "Contenuto misto",
     "MixedContent": "Contenuto misto",
     "Movies": "Film",
     "Movies": "Film",
     "Music": "Musica",
     "Music": "Musica",
-    "MusicVideos": "Video musicali",
+    "MusicVideos": "Video Musicali",
     "NameInstallFailed": "{0} installazione fallita",
     "NameInstallFailed": "{0} installazione fallita",
     "NameSeasonNumber": "Stagione {0}",
     "NameSeasonNumber": "Stagione {0}",
     "NameSeasonUnknown": "Stagione sconosciuta",
     "NameSeasonUnknown": "Stagione sconosciuta",

+ 1 - 1
Emby.Server.Implementations/Localization/Core/ja.json

@@ -15,7 +15,7 @@
     "Favorites": "お気に入り",
     "Favorites": "お気に入り",
     "Folders": "フォルダー",
     "Folders": "フォルダー",
     "Genres": "ジャンル",
     "Genres": "ジャンル",
-    "HeaderAlbumArtists": "アルバムアーティスト",
+    "HeaderAlbumArtists": "アーティストのアルバム",
     "HeaderContinueWatching": "視聴を続ける",
     "HeaderContinueWatching": "視聴を続ける",
     "HeaderFavoriteAlbums": "お気に入りのアルバム",
     "HeaderFavoriteAlbums": "お気に入りのアルバム",
     "HeaderFavoriteArtists": "お気に入りのアーティスト",
     "HeaderFavoriteArtists": "お気に入りのアーティスト",

+ 1 - 1
Emby.Server.Implementations/Localization/Core/kk.json

@@ -15,7 +15,7 @@
     "Favorites": "Tañdaulylar",
     "Favorites": "Tañdaulylar",
     "Folders": "Qaltalar",
     "Folders": "Qaltalar",
     "Genres": "Janrlar",
     "Genres": "Janrlar",
-    "HeaderAlbumArtists": "Älbom oryndauşylary",
+    "HeaderAlbumArtists": "Oryndauşynyñ älbomy",
     "HeaderContinueWatching": "Qaraudy jalğastyru",
     "HeaderContinueWatching": "Qaraudy jalğastyru",
     "HeaderFavoriteAlbums": "Tañdauly älbomdar",
     "HeaderFavoriteAlbums": "Tañdauly älbomdar",
     "HeaderFavoriteArtists": "Tañdauly oryndauşylar",
     "HeaderFavoriteArtists": "Tañdauly oryndauşylar",

+ 1 - 1
Emby.Server.Implementations/Localization/Core/ko.json

@@ -15,7 +15,7 @@
     "Favorites": "즐겨찾기",
     "Favorites": "즐겨찾기",
     "Folders": "폴더",
     "Folders": "폴더",
     "Genres": "장르",
     "Genres": "장르",
-    "HeaderAlbumArtists": "앨범 아티스트",
+    "HeaderAlbumArtists": "아티스트의 앨범",
     "HeaderContinueWatching": "계속 시청하기",
     "HeaderContinueWatching": "계속 시청하기",
     "HeaderFavoriteAlbums": "즐겨찾는 앨범",
     "HeaderFavoriteAlbums": "즐겨찾는 앨범",
     "HeaderFavoriteArtists": "즐겨찾는 아티스트",
     "HeaderFavoriteArtists": "즐겨찾는 아티스트",

+ 1 - 1
Emby.Server.Implementations/Localization/Core/ml.json

@@ -103,7 +103,7 @@
     "ValueSpecialEpisodeName": "പ്രത്യേക - {0}",
     "ValueSpecialEpisodeName": "പ്രത്യേക - {0}",
     "Collections": "ശേഖരങ്ങൾ",
     "Collections": "ശേഖരങ്ങൾ",
     "Folders": "ഫോൾഡറുകൾ",
     "Folders": "ഫോൾഡറുകൾ",
-    "HeaderAlbumArtists": "ആൽബം ആർട്ടിസ്റ്റുകൾ",
+    "HeaderAlbumArtists": "കലാകാരന്റെ ആൽബം",
     "Sync": "സമന്വയിപ്പിക്കുക",
     "Sync": "സമന്വയിപ്പിക്കുക",
     "Movies": "സിനിമകൾ",
     "Movies": "സിനിമകൾ",
     "Photos": "ഫോട്ടോകൾ",
     "Photos": "ഫോട്ടോകൾ",

+ 4 - 3
Emby.Server.Implementations/Localization/Core/pl.json

@@ -15,7 +15,7 @@
     "Favorites": "Ulubione",
     "Favorites": "Ulubione",
     "Folders": "Foldery",
     "Folders": "Foldery",
     "Genres": "Gatunki",
     "Genres": "Gatunki",
-    "HeaderAlbumArtists": "Wykonawcy albumów",
+    "HeaderAlbumArtists": "Album artysty",
     "HeaderContinueWatching": "Kontynuuj odtwarzanie",
     "HeaderContinueWatching": "Kontynuuj odtwarzanie",
     "HeaderFavoriteAlbums": "Ulubione albumy",
     "HeaderFavoriteAlbums": "Ulubione albumy",
     "HeaderFavoriteArtists": "Ulubieni wykonawcy",
     "HeaderFavoriteArtists": "Ulubieni wykonawcy",
@@ -25,7 +25,7 @@
     "HeaderLiveTV": "Telewizja",
     "HeaderLiveTV": "Telewizja",
     "HeaderNextUp": "Do obejrzenia",
     "HeaderNextUp": "Do obejrzenia",
     "HeaderRecordingGroups": "Grupy nagrań",
     "HeaderRecordingGroups": "Grupy nagrań",
-    "HomeVideos": "Nagrania prywatne",
+    "HomeVideos": "Nagrania domowe",
     "Inherit": "Dziedzicz",
     "Inherit": "Dziedzicz",
     "ItemAddedWithName": "{0} zostało dodane do biblioteki",
     "ItemAddedWithName": "{0} zostało dodane do biblioteki",
     "ItemRemovedWithName": "{0} zostało usunięte z biblioteki",
     "ItemRemovedWithName": "{0} zostało usunięte z biblioteki",
@@ -119,5 +119,6 @@
     "Undefined": "Nieustalony",
     "Undefined": "Nieustalony",
     "Forced": "Wymuszony",
     "Forced": "Wymuszony",
     "Default": "Domyślne",
     "Default": "Domyślne",
-    "TaskOptimizeDatabase": "Optymalizuj bazę danych"
+    "TaskOptimizeDatabase": "Optymalizuj bazę danych",
+    "TaskOptimizeDatabaseDescription": "Kompaktuje bazę danych i obcina wolne miejsce. Uruchomienie tego zadania po przeskanowaniu biblioteki lub dokonaniu innych zmian, które pociągają za sobą modyfikacje bazy danych, może poprawić wydajność."
 }
 }

+ 1 - 0
Emby.Server.Implementations/Localization/Core/pr.json

@@ -0,0 +1 @@
+{}

+ 3 - 1
Emby.Server.Implementations/Localization/Core/pt-BR.json

@@ -118,5 +118,7 @@
     "TaskCleanActivityLog": "Limpar Registro de Atividades",
     "TaskCleanActivityLog": "Limpar Registro de Atividades",
     "Undefined": "Indefinido",
     "Undefined": "Indefinido",
     "Forced": "Forçado",
     "Forced": "Forçado",
-    "Default": "Padrão"
+    "Default": "Padrão",
+    "TaskOptimizeDatabaseDescription": "Compactar base de dados e liberar espaço livre. Executar esta tarefa após realizar mudanças que impliquem em modificações da base de dados pode trazer melhorias de desempenho.",
+    "TaskOptimizeDatabase": "Otimizar base de dados"
 }
 }

+ 2 - 1
Emby.Server.Implementations/Localization/Core/pt.json

@@ -117,5 +117,6 @@
     "Undefined": "Indefinido",
     "Undefined": "Indefinido",
     "Forced": "Forçado",
     "Forced": "Forçado",
     "Default": "Predefinição",
     "Default": "Predefinição",
-    "TaskCleanActivityLogDescription": "Apaga itens no registro com idade acima do que é configurado."
+    "TaskCleanActivityLogDescription": "Apaga itens no registro com idade acima do que é configurado.",
+    "TaskOptimizeDatabase": "Otimizar base de dados"
 }
 }

+ 1 - 1
Emby.Server.Implementations/Localization/Core/ru.json

@@ -25,7 +25,7 @@
     "HeaderLiveTV": "Эфир",
     "HeaderLiveTV": "Эфир",
     "HeaderNextUp": "Очередное",
     "HeaderNextUp": "Очередное",
     "HeaderRecordingGroups": "Группы записей",
     "HeaderRecordingGroups": "Группы записей",
-    "HomeVideos": "Домашнее видео",
+    "HomeVideos": "Домашние видео",
     "Inherit": "Наследуемое",
     "Inherit": "Наследуемое",
     "ItemAddedWithName": "{0} - добавлено в медиатеку",
     "ItemAddedWithName": "{0} - добавлено в медиатеку",
     "ItemRemovedWithName": "{0} - изъято из медиатеки",
     "ItemRemovedWithName": "{0} - изъято из медиатеки",

+ 1 - 1
Emby.Server.Implementations/Localization/Core/sk.json

@@ -39,7 +39,7 @@
     "MixedContent": "Zmiešaný obsah",
     "MixedContent": "Zmiešaný obsah",
     "Movies": "Filmy",
     "Movies": "Filmy",
     "Music": "Hudba",
     "Music": "Hudba",
-    "MusicVideos": "Hudobné videoklipy",
+    "MusicVideos": "Hudobné videá",
     "NameInstallFailed": "Inštalácia {0} zlyhala",
     "NameInstallFailed": "Inštalácia {0} zlyhala",
     "NameSeasonNumber": "Séria {0}",
     "NameSeasonNumber": "Séria {0}",
     "NameSeasonUnknown": "Neznáma séria",
     "NameSeasonUnknown": "Neznáma séria",

+ 5 - 3
Emby.Server.Implementations/Localization/Core/sv.json

@@ -15,7 +15,7 @@
     "Favorites": "Favoriter",
     "Favorites": "Favoriter",
     "Folders": "Mappar",
     "Folders": "Mappar",
     "Genres": "Genrer",
     "Genres": "Genrer",
-    "HeaderAlbumArtists": "Albumartister",
+    "HeaderAlbumArtists": "Artistens album",
     "HeaderContinueWatching": "Fortsätt kolla",
     "HeaderContinueWatching": "Fortsätt kolla",
     "HeaderFavoriteAlbums": "Favoritalbum",
     "HeaderFavoriteAlbums": "Favoritalbum",
     "HeaderFavoriteArtists": "Favoritartister",
     "HeaderFavoriteArtists": "Favoritartister",
@@ -25,7 +25,7 @@
     "HeaderLiveTV": "Live-TV",
     "HeaderLiveTV": "Live-TV",
     "HeaderNextUp": "Nästa",
     "HeaderNextUp": "Nästa",
     "HeaderRecordingGroups": "Inspelningsgrupper",
     "HeaderRecordingGroups": "Inspelningsgrupper",
-    "HomeVideos": "Hemvideor",
+    "HomeVideos": "Hemmavideor",
     "Inherit": "Ärv",
     "Inherit": "Ärv",
     "ItemAddedWithName": "{0} lades till i biblioteket",
     "ItemAddedWithName": "{0} lades till i biblioteket",
     "ItemRemovedWithName": "{0} togs bort från biblioteket",
     "ItemRemovedWithName": "{0} togs bort från biblioteket",
@@ -118,5 +118,7 @@
     "TaskCleanActivityLog": "Rensa Aktivitets Logg",
     "TaskCleanActivityLog": "Rensa Aktivitets Logg",
     "Undefined": "odefinierad",
     "Undefined": "odefinierad",
     "Forced": "Tvingad",
     "Forced": "Tvingad",
-    "Default": "Standard"
+    "Default": "Standard",
+    "TaskOptimizeDatabase": "Optimera databasen",
+    "TaskOptimizeDatabaseDescription": "Komprimerar databasen och trunkerar ledigt utrymme. Prestandan kan förbättras genom att köra denna task efter att du har skannat biblioteket eller gjort andra förändringar som indikerar att databasen har modifierats."
 }
 }

+ 2 - 2
Emby.Server.Implementations/Localization/Core/vi.json

@@ -3,7 +3,7 @@
     "Favorites": "Yêu Thích",
     "Favorites": "Yêu Thích",
     "Folders": "Thư Mục",
     "Folders": "Thư Mục",
     "Genres": "Thể Loại",
     "Genres": "Thể Loại",
-    "HeaderAlbumArtists": "Tuyển Tập Nghệ sĩ",
+    "HeaderAlbumArtists": "Album Nghệ sĩ",
     "HeaderContinueWatching": "Xem Tiếp",
     "HeaderContinueWatching": "Xem Tiếp",
     "HeaderLiveTV": "TV Trực Tiếp",
     "HeaderLiveTV": "TV Trực Tiếp",
     "Movies": "Phim",
     "Movies": "Phim",
@@ -82,7 +82,7 @@
     "NameSeasonUnknown": "Không Rõ Mùa",
     "NameSeasonUnknown": "Không Rõ Mùa",
     "NameSeasonNumber": "Phần {0}",
     "NameSeasonNumber": "Phần {0}",
     "NameInstallFailed": "{0} cài đặt thất bại",
     "NameInstallFailed": "{0} cài đặt thất bại",
-    "MusicVideos": "Video Nhạc",
+    "MusicVideos": "Videos Nhạc",
     "Music": "Nhạc",
     "Music": "Nhạc",
     "MixedContent": "Nội dung hỗn hợp",
     "MixedContent": "Nội dung hỗn hợp",
     "MessageServerConfigurationUpdated": "Cấu hình máy chủ đã được cập nhật",
     "MessageServerConfigurationUpdated": "Cấu hình máy chủ đã được cập nhật",

+ 5 - 5
Emby.Server.Implementations/Localization/Core/zh-CN.json

@@ -7,7 +7,7 @@
     "Books": "书籍",
     "Books": "书籍",
     "CameraImageUploadedFrom": "新的相机图像已从 {0} 上传",
     "CameraImageUploadedFrom": "新的相机图像已从 {0} 上传",
     "Channels": "频道",
     "Channels": "频道",
-    "ChapterNameValue": "第 {0} 集",
+    "ChapterNameValue": "章节 {0}",
     "Collections": "合集",
     "Collections": "合集",
     "DeviceOfflineWithName": "{0} 已断开",
     "DeviceOfflineWithName": "{0} 已断开",
     "DeviceOnlineWithName": "{0} 已连接",
     "DeviceOnlineWithName": "{0} 已连接",
@@ -15,8 +15,8 @@
     "Favorites": "我的最爱",
     "Favorites": "我的最爱",
     "Folders": "文件夹",
     "Folders": "文件夹",
     "Genres": "风格",
     "Genres": "风格",
-    "HeaderAlbumArtists": "专辑家",
-    "HeaderContinueWatching": "继续观",
+    "HeaderAlbumArtists": "专辑艺术家",
+    "HeaderContinueWatching": "继续观",
     "HeaderFavoriteAlbums": "收藏的专辑",
     "HeaderFavoriteAlbums": "收藏的专辑",
     "HeaderFavoriteArtists": "最爱的艺术家",
     "HeaderFavoriteArtists": "最爱的艺术家",
     "HeaderFavoriteEpisodes": "最爱的剧集",
     "HeaderFavoriteEpisodes": "最爱的剧集",
@@ -108,8 +108,8 @@
     "TaskCleanLogs": "清理日志目录",
     "TaskCleanLogs": "清理日志目录",
     "TaskRefreshLibraryDescription": "扫描你的媒体库以获取新文件并刷新元数据。",
     "TaskRefreshLibraryDescription": "扫描你的媒体库以获取新文件并刷新元数据。",
     "TaskRefreshLibrary": "扫描媒体库",
     "TaskRefreshLibrary": "扫描媒体库",
-    "TaskRefreshChapterImagesDescription": "为包含剧集的视频提取缩略图。",
-    "TaskRefreshChapterImages": "提取剧集图片",
+    "TaskRefreshChapterImagesDescription": "为包含章节的视频提取缩略图。",
+    "TaskRefreshChapterImages": "提取章节图片",
     "TaskCleanCacheDescription": "删除系统不再需要的缓存文件。",
     "TaskCleanCacheDescription": "删除系统不再需要的缓存文件。",
     "TaskCleanCache": "清理缓存目录",
     "TaskCleanCache": "清理缓存目录",
     "TasksApplicationCategory": "应用程序",
     "TasksApplicationCategory": "应用程序",

+ 6 - 3
Emby.Server.Implementations/Localization/Core/zh-HK.json

@@ -13,7 +13,7 @@
     "DeviceOnlineWithName": "{0} 已經連接",
     "DeviceOnlineWithName": "{0} 已經連接",
     "FailedLoginAttemptWithUserName": "來自 {0} 的登入失敗",
     "FailedLoginAttemptWithUserName": "來自 {0} 的登入失敗",
     "Favorites": "我的最愛",
     "Favorites": "我的最愛",
-    "Folders": "檔案夾",
+    "Folders": "資料夾",
     "Genres": "風格",
     "Genres": "風格",
     "HeaderAlbumArtists": "專輯藝人",
     "HeaderAlbumArtists": "專輯藝人",
     "HeaderContinueWatching": "繼續觀看",
     "HeaderContinueWatching": "繼續觀看",
@@ -39,7 +39,7 @@
     "MixedContent": "混合內容",
     "MixedContent": "混合內容",
     "Movies": "電影",
     "Movies": "電影",
     "Music": "音樂",
     "Music": "音樂",
-    "MusicVideos": "音樂視頻",
+    "MusicVideos": "音樂影片",
     "NameInstallFailed": "{0} 安裝失敗",
     "NameInstallFailed": "{0} 安裝失敗",
     "NameSeasonNumber": "第 {0} 季",
     "NameSeasonNumber": "第 {0} 季",
     "NameSeasonUnknown": "未知季數",
     "NameSeasonUnknown": "未知季數",
@@ -117,5 +117,8 @@
     "TaskCleanActivityLog": "清理活動記錄",
     "TaskCleanActivityLog": "清理活動記錄",
     "Undefined": "未定義",
     "Undefined": "未定義",
     "Forced": "強制",
     "Forced": "強制",
-    "Default": "預設"
+    "Default": "預設",
+    "TaskOptimizeDatabaseDescription": "壓縮數據庫並截斷可用空間。在掃描媒體庫或執行其他數據庫的修改後運行此任務可能會提高性能。",
+    "TaskOptimizeDatabase": "最佳化數據庫",
+    "TaskCleanActivityLogDescription": "刪除早於設定時間的日誌記錄。"
 }
 }

+ 4 - 2
Emby.Server.Implementations/Localization/Core/zh-TW.json

@@ -24,7 +24,7 @@
     "HeaderFavoriteSongs": "最愛歌曲",
     "HeaderFavoriteSongs": "最愛歌曲",
     "HeaderLiveTV": "電視直播",
     "HeaderLiveTV": "電視直播",
     "HeaderNextUp": "接下來",
     "HeaderNextUp": "接下來",
-    "HomeVideos": "自製影片",
+    "HomeVideos": "家庭影片",
     "ItemAddedWithName": "{0} 已新增至媒體庫",
     "ItemAddedWithName": "{0} 已新增至媒體庫",
     "ItemRemovedWithName": "{0} 已從媒體庫移除",
     "ItemRemovedWithName": "{0} 已從媒體庫移除",
     "LabelIpAddressValue": "IP 位址:{0}",
     "LabelIpAddressValue": "IP 位址:{0}",
@@ -117,5 +117,7 @@
     "TaskCleanActivityLog": "清除活動紀錄",
     "TaskCleanActivityLog": "清除活動紀錄",
     "Undefined": "未定義的",
     "Undefined": "未定義的",
     "Forced": "強制",
     "Forced": "強制",
-    "Default": "原本"
+    "Default": "原本",
+    "TaskOptimizeDatabaseDescription": "縮小資料庫並釋放可用空間。在掃描資料庫或進行資料庫相關的更動後使用此功能會增加效能。",
+    "TaskOptimizeDatabase": "最佳化資料庫"
 }
 }

+ 27 - 36
Emby.Server.Implementations/Localization/LocalizationManager.cs

@@ -1,5 +1,3 @@
-#nullable disable
-
 using System;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -38,10 +36,10 @@ namespace Emby.Server.Implementations.Localization
         private readonly ConcurrentDictionary<string, Dictionary<string, string>> _dictionaries =
         private readonly ConcurrentDictionary<string, Dictionary<string, string>> _dictionaries =
             new ConcurrentDictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
             new ConcurrentDictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
 
 
-        private List<CultureDto> _cultures;
-
         private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
         private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
 
 
+        private List<CultureDto> _cultures = new List<CultureDto>();
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="LocalizationManager" /> class.
         /// Initializes a new instance of the <see cref="LocalizationManager" /> class.
         /// </summary>
         /// </summary>
@@ -72,8 +70,8 @@ namespace Emby.Server.Implementations.Localization
                 string countryCode = resource.Substring(RatingsPath.Length, 2);
                 string countryCode = resource.Substring(RatingsPath.Length, 2);
                 var dict = new Dictionary<string, ParentalRating>(StringComparer.OrdinalIgnoreCase);
                 var dict = new Dictionary<string, ParentalRating>(StringComparer.OrdinalIgnoreCase);
 
 
-                await using var str = _assembly.GetManifestResourceStream(resource);
-                using var reader = new StreamReader(str);
+                await using var stream = _assembly.GetManifestResourceStream(resource);
+                using var reader = new StreamReader(stream!); // shouldn't be null here, we just got the resource path from Assembly.GetManifestResourceNames()
                 await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
                 await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
                 {
                 {
                     if (string.IsNullOrWhiteSpace(line))
                     if (string.IsNullOrWhiteSpace(line))
@@ -113,7 +111,8 @@ namespace Emby.Server.Implementations.Localization
         {
         {
             List<CultureDto> list = new List<CultureDto>();
             List<CultureDto> list = new List<CultureDto>();
 
 
-            await using var stream = _assembly.GetManifestResourceStream(CulturesPath);
+            await using var stream = _assembly.GetManifestResourceStream(CulturesPath)
+                ?? throw new InvalidOperationException($"Invalid resource path: '{CulturesPath}'");
             using var reader = new StreamReader(stream);
             using var reader = new StreamReader(stream);
             await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
             await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
             {
             {
@@ -162,7 +161,7 @@ namespace Emby.Server.Implementations.Localization
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public CultureDto FindLanguageInfo(string language)
+        public CultureDto? FindLanguageInfo(string language)
         {
         {
             // TODO language should ideally be a ReadOnlySpan but moq cannot mock ref structs
             // TODO language should ideally be a ReadOnlySpan but moq cannot mock ref structs
             for (var i = 0; i < _cultures.Count; i++)
             for (var i = 0; i < _cultures.Count; i++)
@@ -183,9 +182,10 @@ namespace Emby.Server.Implementations.Localization
         /// <inheritdoc />
         /// <inheritdoc />
         public IEnumerable<CountryInfo> GetCountries()
         public IEnumerable<CountryInfo> GetCountries()
         {
         {
-            using StreamReader reader = new StreamReader(_assembly.GetManifestResourceStream(CountriesPath));
-
-            return JsonSerializer.Deserialize<IEnumerable<CountryInfo>>(reader.ReadToEnd(), _jsonOptions);
+            using StreamReader reader = new StreamReader(
+                _assembly.GetManifestResourceStream(CountriesPath) ?? throw new InvalidOperationException($"Invalid resource path: '{CountriesPath}'"));
+            return JsonSerializer.Deserialize<IEnumerable<CountryInfo>>(reader.ReadToEnd(), _jsonOptions)
+                ?? throw new InvalidOperationException($"Resource contains invalid data: '{CountriesPath}'");
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
@@ -205,7 +205,9 @@ namespace Emby.Server.Implementations.Localization
                 countryCode = "us";
                 countryCode = "us";
             }
             }
 
 
-            return GetRatings(countryCode) ?? GetRatings("us");
+            return GetRatings(countryCode)
+                ?? GetRatings("us")
+                ?? throw new InvalidOperationException($"Invalid resource path: '{CountriesPath}'");
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -213,7 +215,7 @@ namespace Emby.Server.Implementations.Localization
         /// </summary>
         /// </summary>
         /// <param name="countryCode">The country code.</param>
         /// <param name="countryCode">The country code.</param>
         /// <returns>The ratings.</returns>
         /// <returns>The ratings.</returns>
-        private Dictionary<string, ParentalRating> GetRatings(string countryCode)
+        private Dictionary<string, ParentalRating>? GetRatings(string countryCode)
         {
         {
             _allParentalRatings.TryGetValue(countryCode, out var value);
             _allParentalRatings.TryGetValue(countryCode, out var value);
 
 
@@ -238,7 +240,7 @@ namespace Emby.Server.Implementations.Localization
 
 
             var ratingsDictionary = GetParentalRatingsDictionary();
             var ratingsDictionary = GetParentalRatingsDictionary();
 
 
-            if (ratingsDictionary.TryGetValue(rating, out ParentalRating value))
+            if (ratingsDictionary.TryGetValue(rating, out ParentalRating? value))
             {
             {
                 return value.Value;
                 return value.Value;
             }
             }
@@ -268,20 +270,6 @@ namespace Emby.Server.Implementations.Localization
             return null;
             return null;
         }
         }
 
 
-        /// <inheritdoc />
-        public bool HasUnicodeCategory(string value, UnicodeCategory category)
-        {
-            foreach (var chr in value)
-            {
-                if (char.GetUnicodeCategory(chr) == category)
-                {
-                    return true;
-                }
-            }
-
-            return false;
-        }
-
         /// <inheritdoc />
         /// <inheritdoc />
         public string GetLocalizedString(string phrase)
         public string GetLocalizedString(string phrase)
         {
         {
@@ -347,18 +335,21 @@ namespace Emby.Server.Implementations.Localization
         {
         {
             await using var stream = _assembly.GetManifestResourceStream(resourcePath);
             await using var stream = _assembly.GetManifestResourceStream(resourcePath);
             // If a Culture doesn't have a translation the stream will be null and it defaults to en-us further up the chain
             // If a Culture doesn't have a translation the stream will be null and it defaults to en-us further up the chain
-            if (stream != null)
+            if (stream == null)
             {
             {
-                var dict = await JsonSerializer.DeserializeAsync<Dictionary<string, string>>(stream, _jsonOptions).ConfigureAwait(false);
+                _logger.LogError("Missing translation/culture resource: {ResourcePath}", resourcePath);
+                return;
+            }
 
 
-                foreach (var key in dict.Keys)
-                {
-                    dictionary[key] = dict[key];
-                }
+            var dict = await JsonSerializer.DeserializeAsync<Dictionary<string, string>>(stream, _jsonOptions).ConfigureAwait(false);
+            if (dict == null)
+            {
+                throw new InvalidOperationException($"Resource contains invalid data: '{stream}'");
             }
             }
-            else
+
+            foreach (var key in dict.Keys)
             {
             {
-                _logger.LogError("Missing translation/culture resource: {ResourcePath}", resourcePath);
+                dictionary[key] = dict[key];
             }
             }
         }
         }
 
 

+ 5 - 0
Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs

@@ -49,5 +49,10 @@ namespace Emby.Server.Implementations.Playlists
             query.Parent = null;
             query.Parent = null;
             return LibraryManager.GetItemsResult(query);
             return LibraryManager.GetItemsResult(query);
         }
         }
+
+        public override string GetClientTypeName()
+        {
+            return "ManualPlaylistsFolder";
+        }
     }
     }
 }
 }

+ 3 - 6
Jellyfin.Api/Controllers/LiveTvController.cs

@@ -1172,7 +1172,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesVideoFile]
         [ProducesVideoFile]
-        public async Task<ActionResult> GetLiveRecordingFile([FromRoute, Required] string recordingId)
+        public ActionResult GetLiveRecordingFile([FromRoute, Required] string recordingId)
         {
         {
             var path = _liveTvManager.GetEmbyTvActiveRecordingPath(recordingId);
             var path = _liveTvManager.GetEmbyTvActiveRecordingPath(recordingId);
 
 
@@ -1181,11 +1181,8 @@ namespace Jellyfin.Api.Controllers
                 return NotFound();
                 return NotFound();
             }
             }
 
 
-            await using var memoryStream = new MemoryStream();
-            await new ProgressiveFileCopier(path, null, _transcodingJobHelper, CancellationToken.None)
-                .WriteToAsync(memoryStream, CancellationToken.None)
-                .ConfigureAwait(false);
-            return File(memoryStream, MimeTypes.GetMimeType(path));
+            var stream = new ProgressiveFileStream(path, null, _transcodingJobHelper);
+            return new FileStreamResult(stream, MimeTypes.GetMimeType(path));
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 8 - 3
Jellyfin.Api/Helpers/ProgressiveFileStream.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.Diagnostics;
 using System.IO;
 using System.IO;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
 using System.Threading;
 using System.Threading;
@@ -16,6 +17,7 @@ namespace Jellyfin.Api.Helpers
         private readonly FileStream _fileStream;
         private readonly FileStream _fileStream;
         private readonly TranscodingJobDto? _job;
         private readonly TranscodingJobDto? _job;
         private readonly TranscodingJobHelper _transcodingJobHelper;
         private readonly TranscodingJobHelper _transcodingJobHelper;
+        private readonly int _timeoutMs;
         private readonly bool _allowAsyncFileRead;
         private readonly bool _allowAsyncFileRead;
         private int _bytesWritten;
         private int _bytesWritten;
         private bool _disposed;
         private bool _disposed;
@@ -26,10 +28,12 @@ namespace Jellyfin.Api.Helpers
         /// <param name="filePath">The path to the transcoded file.</param>
         /// <param name="filePath">The path to the transcoded file.</param>
         /// <param name="job">The transcoding job information.</param>
         /// <param name="job">The transcoding job information.</param>
         /// <param name="transcodingJobHelper">The transcoding job helper.</param>
         /// <param name="transcodingJobHelper">The transcoding job helper.</param>
-        public ProgressiveFileStream(string filePath, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper)
+        /// <param name="timeoutMs">The timeout duration in milliseconds.</param>
+        public ProgressiveFileStream(string filePath, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper, int timeoutMs = 30000)
         {
         {
             _job = job;
             _job = job;
             _transcodingJobHelper = transcodingJobHelper;
             _transcodingJobHelper = transcodingJobHelper;
+            _timeoutMs = timeoutMs;
             _bytesWritten = 0;
             _bytesWritten = 0;
 
 
             var fileOptions = FileOptions.SequentialScan;
             var fileOptions = FileOptions.SequentialScan;
@@ -81,6 +85,7 @@ namespace Jellyfin.Api.Helpers
         {
         {
             int totalBytesRead = 0;
             int totalBytesRead = 0;
             int remainingBytesToRead = count;
             int remainingBytesToRead = count;
+            var stopwatch = Stopwatch.StartNew();
 
 
             int newOffset = offset;
             int newOffset = offset;
             while (remainingBytesToRead > 0)
             while (remainingBytesToRead > 0)
@@ -111,8 +116,8 @@ namespace Jellyfin.Api.Helpers
                 }
                 }
                 else
                 else
                 {
                 {
-                    // If the job is null it's a live stream and will require user action to close
-                    if (_job?.HasExited ?? false)
+                    // If the job is null it's a live stream and will require user action to close, but don't keep it open indefinitely
+                    if (_job?.HasExited ?? stopwatch.ElapsedMilliseconds > _timeoutMs)
                     {
                     {
                         break;
                         break;
                     }
                     }

+ 1 - 1
Jellyfin.Api/Jellyfin.Api.csproj

@@ -14,7 +14,7 @@
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.8" />
+    <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.9" />
     <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
     <PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.5" />
     <PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.5" />
     <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.1.5" />
     <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.1.5" />

+ 16 - 1
Jellyfin.Data/Enums/BaseItemKind.cs

@@ -78,6 +78,16 @@
         /// </summary>
         /// </summary>
         Movie,
         Movie,
 
 
+        /// <summary>
+        /// Item is a live tv channel.
+        /// </summary>
+        LiveTvChannel,
+
+        /// <summary>
+        /// Item is a live tv program.
+        /// </summary>
+        LiveTvProgram,
+
         /// <summary>
         /// <summary>
         /// Item is music album.
         /// Item is music album.
         /// </summary>
         /// </summary>
@@ -118,6 +128,11 @@
         /// </summary>
         /// </summary>
         Playlist,
         Playlist,
 
 
+        /// <summary>
+        /// Item is playlist folder.
+        /// </summary>
+        PlaylistsFolder,
+
         /// <summary>
         /// <summary>
         /// Item is program
         /// Item is program
         /// </summary>
         /// </summary>
@@ -187,4 +202,4 @@
         /// </summary>
         /// </summary>
         Year
         Year
     }
     }
-}
+}

+ 4 - 4
Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj

@@ -19,13 +19,13 @@
 
 
   <ItemGroup>
   <ItemGroup>
     <PackageReference Include="System.Linq.Async" Version="5.0.0" />
     <PackageReference Include="System.Linq.Async" Version="5.0.0" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.8" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.8" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.8">
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.9" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.9" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.9">
       <PrivateAssets>all</PrivateAssets>
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
     </PackageReference>
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.8">
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.9">
       <PrivateAssets>all</PrivateAssets>
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
     </PackageReference>

+ 6 - 6
Jellyfin.Server/Jellyfin.Server.csproj

@@ -33,18 +33,18 @@
     <PackageReference Include="CommandLineParser" Version="2.8.0" />
     <PackageReference Include="CommandLineParser" Version="2.8.0" />
     <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
-    <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.8" />
-    <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.8" />
-    <PackageReference Include="prometheus-net" Version="4.2.0" />
-    <PackageReference Include="prometheus-net.AspNetCore" Version="4.2.0" />
+    <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.9" />
+    <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.9" />
+    <PackageReference Include="prometheus-net" Version="5.0.1" />
+    <PackageReference Include="prometheus-net.AspNetCore" Version="5.0.1" />
     <PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
     <PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
     <PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
     <PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
-    <PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
+    <PackageReference Include="Serilog.Settings.Configuration" Version="3.2.0" />
     <PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
     <PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
     <PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
     <PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
     <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
     <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
     <PackageReference Include="Serilog.Sinks.Graylog" Version="2.2.2" />
     <PackageReference Include="Serilog.Sinks.Graylog" Version="2.2.2" />
-    <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.4" />
+    <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.5" />
   </ItemGroup>
   </ItemGroup>
 
 
   <ItemGroup>
   <ItemGroup>

+ 24 - 24
MediaBrowser.Common/Net/IPHost.cs

@@ -4,7 +4,6 @@ using System.Linq;
 using System.Net;
 using System.Net;
 using System.Net.Sockets;
 using System.Net.Sockets;
 using System.Text.RegularExpressions;
 using System.Text.RegularExpressions;
-using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Common.Net
 namespace MediaBrowser.Common.Net
 {
 {
@@ -196,7 +195,7 @@ namespace MediaBrowser.Common.Net
                 return res;
                 return res;
             }
             }
 
 
-            throw new InvalidCastException("Host does not contain a valid value. {host}");
+            throw new InvalidCastException($"Host does not contain a valid value. {host}");
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -221,7 +220,7 @@ namespace MediaBrowser.Common.Net
                 return res;
                 return res;
             }
             }
 
 
-            throw new InvalidCastException("Host does not contain a valid value. {host}");
+            throw new InvalidCastException($"Host does not contain a valid value. {host}");
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -349,7 +348,7 @@ namespace MediaBrowser.Common.Net
                     }
                     }
                 }
                 }
 
 
-                output = output[0..^1];
+                output = output[..^1];
 
 
                 if (moreThanOne)
                 if (moreThanOne)
                 {
                 {
@@ -400,7 +399,7 @@ namespace MediaBrowser.Common.Net
             if ((_addresses.Length == 0 && !Resolved) || (DateTime.UtcNow > _lastResolved.Value.AddMinutes(Timeout)))
             if ((_addresses.Length == 0 && !Resolved) || (DateTime.UtcNow > _lastResolved.Value.AddMinutes(Timeout)))
             {
             {
                 _lastResolved = DateTime.UtcNow;
                 _lastResolved = DateTime.UtcNow;
-                ResolveHostInternal().GetAwaiter().GetResult();
+                ResolveHostInternal();
                 Resolved = true;
                 Resolved = true;
             }
             }
 
 
@@ -410,30 +409,31 @@ namespace MediaBrowser.Common.Net
         /// <summary>
         /// <summary>
         /// Task that looks up a Host name and returns its IP addresses.
         /// Task that looks up a Host name and returns its IP addresses.
         /// </summary>
         /// </summary>
-        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
-        private async Task ResolveHostInternal()
+        private void ResolveHostInternal()
         {
         {
-            if (!string.IsNullOrEmpty(HostName))
+            var hostName = HostName;
+            if (string.IsNullOrEmpty(hostName))
             {
             {
-                // Resolves the host name - so save a DNS lookup.
-                if (string.Equals(HostName, "localhost", StringComparison.OrdinalIgnoreCase))
+                return;
+            }
+
+            // Resolves the host name - so save a DNS lookup.
+            if (string.Equals(hostName, "localhost", StringComparison.OrdinalIgnoreCase))
+            {
+                _addresses = new IPAddress[] { IPAddress.Loopback, IPAddress.IPv6Loopback };
+                return;
+            }
+
+            if (Uri.CheckHostName(hostName) == UriHostNameType.Dns)
+            {
+                try
                 {
                 {
-                    _addresses = new IPAddress[] { IPAddress.Loopback, IPAddress.IPv6Loopback };
-                    return;
+                    _addresses = Dns.GetHostEntry(hostName).AddressList;
                 }
                 }
-
-                if (Uri.CheckHostName(HostName).Equals(UriHostNameType.Dns))
+                catch (SocketException ex)
                 {
                 {
-                    try
-                    {
-                        IPHostEntry ip = await Dns.GetHostEntryAsync(HostName).ConfigureAwait(false);
-                        _addresses = ip.AddressList;
-                    }
-                    catch (SocketException ex)
-                    {
-                        // Log and then ignore socket errors, as the result value will just be an empty array.
-                        Debug.WriteLine("GetHostEntryAsync failed with {Message}.", ex.Message);
-                    }
+                    // Log and then ignore socket errors, as the result value will just be an empty array.
+                    Debug.WriteLine("GetHostAddresses failed with {Message}.", ex.Message);
                 }
                 }
             }
             }
         }
         }

+ 2 - 2
MediaBrowser.Common/Plugins/BasePluginOfT.cs

@@ -47,10 +47,10 @@ namespace MediaBrowser.Common.Plugins
             var assemblyFilePath = assembly.Location;
             var assemblyFilePath = assembly.Location;
 
 
             var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath));
             var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath));
-            if (!Directory.Exists(dataFolderPath) && Version != null)
+            if (Version != null && !Directory.Exists(dataFolderPath))
             {
             {
                 // Try again with the version number appended to the folder name.
                 // Try again with the version number appended to the folder name.
-                dataFolderPath = dataFolderPath + "_" + Version.ToString();
+                dataFolderPath += "_" + Version.ToString();
             }
             }
 
 
             SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version);
             SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version);

+ 4 - 4
MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs

@@ -1,6 +1,5 @@
-#nullable disable
-
 using System;
 using System;
+using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
 using Jellyfin.Extensions;
 using Jellyfin.Extensions;
@@ -16,7 +15,7 @@ namespace MediaBrowser.Controller.BaseItemManager
     {
     {
         private readonly IServerConfigurationManager _serverConfigurationManager;
         private readonly IServerConfigurationManager _serverConfigurationManager;
 
 
-        private int _metadataRefreshConcurrency = 0;
+        private int _metadataRefreshConcurrency;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseItemManager"/> class.
         /// Initializes a new instance of the <see cref="BaseItemManager"/> class.
@@ -101,7 +100,7 @@ namespace MediaBrowser.Controller.BaseItemManager
         /// Called when the configuration is updated.
         /// Called when the configuration is updated.
         /// It will refresh the metadata throttler if the relevant config changed.
         /// It will refresh the metadata throttler if the relevant config changed.
         /// </summary>
         /// </summary>
-        private void OnConfigurationUpdated(object sender, EventArgs e)
+        private void OnConfigurationUpdated(object? sender, EventArgs e)
         {
         {
             int newMetadataRefreshConcurrency = GetMetadataRefreshConcurrency();
             int newMetadataRefreshConcurrency = GetMetadataRefreshConcurrency();
             if (_metadataRefreshConcurrency != newMetadataRefreshConcurrency)
             if (_metadataRefreshConcurrency != newMetadataRefreshConcurrency)
@@ -114,6 +113,7 @@ namespace MediaBrowser.Controller.BaseItemManager
         /// <summary>
         /// <summary>
         /// Creates the metadata refresh throttler.
         /// Creates the metadata refresh throttler.
         /// </summary>
         /// </summary>
+        [MemberNotNull(nameof(MetadataRefreshThrottler))]
         private void SetupMetadataThrottler()
         private void SetupMetadataThrottler()
         {
         {
             MetadataRefreshThrottler = new SemaphoreSlim(_metadataRefreshConcurrency);
             MetadataRefreshThrottler = new SemaphoreSlim(_metadataRefreshConcurrency);

+ 1 - 3
MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs

@@ -1,5 +1,3 @@
-#nullable disable
-
 using System.Threading;
 using System.Threading;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Configuration;
@@ -34,4 +32,4 @@ namespace MediaBrowser.Controller.BaseItemManager
         /// <returns><c>true</c> if image fetcher is enabled, else false.</returns>
         /// <returns><c>true</c> if image fetcher is enabled, else false.</returns>
         bool IsImageFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name);
         bool IsImageFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name);
     }
     }
-}
+}

+ 4 - 5
MediaBrowser.Controller/Channels/ChannelItemResult.cs

@@ -1,7 +1,6 @@
-#nullable disable
-
-#pragma warning disable CA1002, CA2227, CS1591
+#pragma warning disable CS1591
 
 
+using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 
 
 namespace MediaBrowser.Controller.Channels
 namespace MediaBrowser.Controller.Channels
@@ -10,10 +9,10 @@ namespace MediaBrowser.Controller.Channels
     {
     {
         public ChannelItemResult()
         public ChannelItemResult()
         {
         {
-            Items = new List<ChannelItemInfo>();
+            Items = Array.Empty<ChannelItemInfo>();
         }
         }
 
 
-        public List<ChannelItemInfo> Items { get; set; }
+        public IReadOnlyList<ChannelItemInfo> Items { get; set; }
 
 
         public int? TotalRecordCount { get; set; }
         public int? TotalRecordCount { get; set; }
     }
     }

+ 1 - 1
MediaBrowser.Controller/Collections/CollectionCreationOptions.cs

@@ -1,6 +1,6 @@
 #nullable disable
 #nullable disable
 
 
-#pragma warning disable CA2227, CS1591
+#pragma warning disable CS1591
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;

+ 0 - 2
MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs

@@ -1,5 +1,3 @@
-#nullable disable
-
 #pragma warning disable CS1591
 #pragma warning disable CS1591
 
 
 using System;
 using System;

+ 3 - 5
MediaBrowser.Controller/Collections/ICollectionManager.cs

@@ -1,5 +1,3 @@
-#nullable disable
-
 #pragma warning disable CS1591
 #pragma warning disable CS1591
 
 
 using System;
 using System;
@@ -16,17 +14,17 @@ namespace MediaBrowser.Controller.Collections
         /// <summary>
         /// <summary>
         /// Occurs when [collection created].
         /// Occurs when [collection created].
         /// </summary>
         /// </summary>
-        event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
+        event EventHandler<CollectionCreatedEventArgs>? CollectionCreated;
 
 
         /// <summary>
         /// <summary>
         /// Occurs when [items added to collection].
         /// Occurs when [items added to collection].
         /// </summary>
         /// </summary>
-        event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
+        event EventHandler<CollectionModifiedEventArgs>? ItemsAddedToCollection;
 
 
         /// <summary>
         /// <summary>
         /// Occurs when [items removed from collection].
         /// Occurs when [items removed from collection].
         /// </summary>
         /// </summary>
-        event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
+        event EventHandler<CollectionModifiedEventArgs>? ItemsRemovedFromCollection;
 
 
         /// <summary>
         /// <summary>
         /// Creates the collection.
         /// Creates the collection.

+ 0 - 2
MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs

@@ -1,5 +1,3 @@
-#nullable disable
-
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Configuration;
 
 

+ 3 - 5
MediaBrowser.Controller/Dlna/IDlnaManager.cs

@@ -1,5 +1,3 @@
-#nullable disable
-
 #pragma warning disable CS1591
 #pragma warning disable CS1591
 
 
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -22,7 +20,7 @@ namespace MediaBrowser.Controller.Dlna
         /// </summary>
         /// </summary>
         /// <param name="headers">The headers.</param>
         /// <param name="headers">The headers.</param>
         /// <returns>DeviceProfile.</returns>
         /// <returns>DeviceProfile.</returns>
-        DeviceProfile GetProfile(IHeaderDictionary headers);
+        DeviceProfile? GetProfile(IHeaderDictionary headers);
 
 
         /// <summary>
         /// <summary>
         /// Gets the default profile.
         /// Gets the default profile.
@@ -53,14 +51,14 @@ namespace MediaBrowser.Controller.Dlna
         /// </summary>
         /// </summary>
         /// <param name="id">The identifier.</param>
         /// <param name="id">The identifier.</param>
         /// <returns>DeviceProfile.</returns>
         /// <returns>DeviceProfile.</returns>
-        DeviceProfile GetProfile(string id);
+        DeviceProfile? GetProfile(string id);
 
 
         /// <summary>
         /// <summary>
         /// Gets the profile.
         /// Gets the profile.
         /// </summary>
         /// </summary>
         /// <param name="deviceInfo">The device information.</param>
         /// <param name="deviceInfo">The device information.</param>
         /// <returns>DeviceProfile.</returns>
         /// <returns>DeviceProfile.</returns>
-        DeviceProfile GetProfile(DeviceIdentification deviceInfo);
+        DeviceProfile? GetProfile(DeviceIdentification deviceInfo);
 
 
         /// <summary>
         /// <summary>
         /// Gets the server description XML.
         /// Gets the server description XML.

+ 21 - 22
MediaBrowser.Controller/Entities/AggregateFolder.cs

@@ -1,6 +1,6 @@
 #nullable disable
 #nullable disable
 
 
-#pragma warning disable CS1591
+#pragma warning disable CA1819, CS1591
 
 
 using System;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
@@ -18,52 +18,51 @@ namespace MediaBrowser.Controller.Entities
 {
 {
     /// <summary>
     /// <summary>
     /// Specialized folder that can have items added to it's children by external entities.
     /// Specialized folder that can have items added to it's children by external entities.
-    /// Used for our RootFolder so plug-ins can add items.
+    /// Used for our RootFolder so plugins can add items.
     /// </summary>
     /// </summary>
     public class AggregateFolder : Folder
     public class AggregateFolder : Folder
     {
     {
+        private readonly object _childIdsLock = new object();
+
+        /// <summary>
+        /// The _virtual children.
+        /// </summary>
+        private readonly ConcurrentBag<BaseItem> _virtualChildren = new ConcurrentBag<BaseItem>();
         private bool _requiresRefresh;
         private bool _requiresRefresh;
+        private Guid[] _childrenIds = null;
 
 
         public AggregateFolder()
         public AggregateFolder()
         {
         {
             PhysicalLocationsList = Array.Empty<string>();
             PhysicalLocationsList = Array.Empty<string>();
         }
         }
 
 
-        [JsonIgnore]
-        public override bool IsPhysicalRoot => true;
-
-        public override bool CanDelete()
-        {
-            return false;
-        }
-
-        [JsonIgnore]
-        public override bool SupportsPlayedStatus => false;
-
-        /// <summary>
-        /// The _virtual children.
-        /// </summary>
-        private readonly ConcurrentBag<BaseItem> _virtualChildren = new ConcurrentBag<BaseItem>();
-
         /// <summary>
         /// <summary>
         /// Gets the virtual children.
         /// Gets the virtual children.
         /// </summary>
         /// </summary>
         /// <value>The virtual children.</value>
         /// <value>The virtual children.</value>
         public ConcurrentBag<BaseItem> VirtualChildren => _virtualChildren;
         public ConcurrentBag<BaseItem> VirtualChildren => _virtualChildren;
 
 
+        [JsonIgnore]
+        public override bool IsPhysicalRoot => true;
+
+        [JsonIgnore]
+        public override bool SupportsPlayedStatus => false;
+
         [JsonIgnore]
         [JsonIgnore]
         public override string[] PhysicalLocations => PhysicalLocationsList;
         public override string[] PhysicalLocations => PhysicalLocationsList;
 
 
         public string[] PhysicalLocationsList { get; set; }
         public string[] PhysicalLocationsList { get; set; }
 
 
+        public override bool CanDelete()
+        {
+            return false;
+        }
+
         protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService)
         protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService)
         {
         {
             return CreateResolveArgs(directoryService, true).FileSystemChildren;
             return CreateResolveArgs(directoryService, true).FileSystemChildren;
         }
         }
 
 
-        private Guid[] _childrenIds = null;
-        private readonly object _childIdsLock = new object();
-
         protected override List<BaseItem> LoadChildren()
         protected override List<BaseItem> LoadChildren()
         {
         {
             lock (_childIdsLock)
             lock (_childIdsLock)
@@ -169,7 +168,7 @@ namespace MediaBrowser.Controller.Entities
         /// Adds the virtual child.
         /// Adds the virtual child.
         /// </summary>
         /// </summary>
         /// <param name="child">The child.</param>
         /// <param name="child">The child.</param>
-        /// <exception cref="ArgumentNullException"></exception>
+        /// <exception cref="ArgumentNullException">Throws if child is null.</exception>
         public void AddVirtualChild(BaseItem child)
         public void AddVirtualChild(BaseItem child)
         {
         {
             if (child == null)
             if (child == null)

+ 17 - 17
MediaBrowser.Controller/Entities/Audio/Audio.cs

@@ -1,6 +1,6 @@
 #nullable disable
 #nullable disable
 
 
-#pragma warning disable CS1591
+#pragma warning disable CA1002, CA1724, CA1826, CS1591
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -25,6 +25,12 @@ namespace MediaBrowser.Controller.Entities.Audio
         IHasLookupInfo<SongInfo>,
         IHasLookupInfo<SongInfo>,
         IHasMediaSources
         IHasMediaSources
     {
     {
+        public Audio()
+        {
+            Artists = Array.Empty<string>();
+            AlbumArtists = Array.Empty<string>();
+        }
+
         /// <inheritdoc />
         /// <inheritdoc />
         [JsonIgnore]
         [JsonIgnore]
         public IReadOnlyList<string> Artists { get; set; }
         public IReadOnlyList<string> Artists { get; set; }
@@ -33,17 +39,6 @@ namespace MediaBrowser.Controller.Entities.Audio
         [JsonIgnore]
         [JsonIgnore]
         public IReadOnlyList<string> AlbumArtists { get; set; }
         public IReadOnlyList<string> AlbumArtists { get; set; }
 
 
-        public Audio()
-        {
-            Artists = Array.Empty<string>();
-            AlbumArtists = Array.Empty<string>();
-        }
-
-        public override double GetDefaultPrimaryImageAspectRatio()
-        {
-            return 1;
-        }
-
         [JsonIgnore]
         [JsonIgnore]
         public override bool SupportsPlayedStatus => true;
         public override bool SupportsPlayedStatus => true;
 
 
@@ -62,11 +57,6 @@ namespace MediaBrowser.Controller.Entities.Audio
         [JsonIgnore]
         [JsonIgnore]
         public override Folder LatestItemsIndexContainer => AlbumEntity;
         public override Folder LatestItemsIndexContainer => AlbumEntity;
 
 
-        public override bool CanDownload()
-        {
-            return IsFileProtocol;
-        }
-
         [JsonIgnore]
         [JsonIgnore]
         public MusicAlbum AlbumEntity => FindParent<MusicAlbum>();
         public MusicAlbum AlbumEntity => FindParent<MusicAlbum>();
 
 
@@ -77,6 +67,16 @@ namespace MediaBrowser.Controller.Entities.Audio
         [JsonIgnore]
         [JsonIgnore]
         public override string MediaType => Model.Entities.MediaType.Audio;
         public override string MediaType => Model.Entities.MediaType.Audio;
 
 
+        public override double GetDefaultPrimaryImageAspectRatio()
+        {
+            return 1;
+        }
+
+        public override bool CanDownload()
+        {
+            return IsFileProtocol;
+        }
+
         /// <summary>
         /// <summary>
         /// Creates the name of the sort.
         /// Creates the name of the sort.
         /// </summary>
         /// </summary>

+ 1 - 1
MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs

@@ -1,6 +1,6 @@
 #nullable disable
 #nullable disable
 
 
-#pragma warning disable CS1591
+#pragma warning disable CA1819, CS1591
 
 
 namespace MediaBrowser.Controller.Entities.Audio
 namespace MediaBrowser.Controller.Entities.Audio
 {
 {

+ 26 - 26
MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs

@@ -1,6 +1,6 @@
 #nullable disable
 #nullable disable
 
 
-#pragma warning disable CS1591
+#pragma warning disable CA1721, CA1826, CS1591
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -23,18 +23,18 @@ namespace MediaBrowser.Controller.Entities.Audio
     /// </summary>
     /// </summary>
     public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<AlbumInfo>, IMetadataContainer
     public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<AlbumInfo>, IMetadataContainer
     {
     {
-        /// <inheritdoc />
-        public IReadOnlyList<string> AlbumArtists { get; set; }
-
-        /// <inheritdoc />
-        public IReadOnlyList<string> Artists { get; set; }
-
         public MusicAlbum()
         public MusicAlbum()
         {
         {
             Artists = Array.Empty<string>();
             Artists = Array.Empty<string>();
             AlbumArtists = Array.Empty<string>();
             AlbumArtists = Array.Empty<string>();
         }
         }
 
 
+        /// <inheritdoc />
+        public IReadOnlyList<string> AlbumArtists { get; set; }
+
+        /// <inheritdoc />
+        public IReadOnlyList<string> Artists { get; set; }
+
         [JsonIgnore]
         [JsonIgnore]
         public override bool SupportsAddingToPlaylist => true;
         public override bool SupportsAddingToPlaylist => true;
 
 
@@ -44,6 +44,25 @@ namespace MediaBrowser.Controller.Entities.Audio
         [JsonIgnore]
         [JsonIgnore]
         public MusicArtist MusicArtist => GetMusicArtist(new DtoOptions(true));
         public MusicArtist MusicArtist => GetMusicArtist(new DtoOptions(true));
 
 
+        [JsonIgnore]
+        public override bool SupportsPlayedStatus => false;
+
+        [JsonIgnore]
+        public override bool SupportsCumulativeRunTimeTicks => true;
+
+        [JsonIgnore]
+        public string AlbumArtist => AlbumArtists.FirstOrDefault();
+
+        [JsonIgnore]
+        public override bool SupportsPeople => false;
+
+        /// <summary>
+        /// Gets the tracks.
+        /// </summary>
+        /// <value>The tracks.</value>
+        [JsonIgnore]
+        public IEnumerable<Audio> Tracks => GetRecursiveChildren(i => i is Audio).Cast<Audio>();
+
         public MusicArtist GetMusicArtist(DtoOptions options)
         public MusicArtist GetMusicArtist(DtoOptions options)
         {
         {
             var parents = GetParents();
             var parents = GetParents();
@@ -64,25 +83,6 @@ namespace MediaBrowser.Controller.Entities.Audio
             return null;
             return null;
         }
         }
 
 
-        [JsonIgnore]
-        public override bool SupportsPlayedStatus => false;
-
-        [JsonIgnore]
-        public override bool SupportsCumulativeRunTimeTicks => true;
-
-        [JsonIgnore]
-        public string AlbumArtist => AlbumArtists.FirstOrDefault();
-
-        [JsonIgnore]
-        public override bool SupportsPeople => false;
-
-        /// <summary>
-        /// Gets the tracks.
-        /// </summary>
-        /// <value>The tracks.</value>
-        [JsonIgnore]
-        public IEnumerable<Audio> Tracks => GetRecursiveChildren(i => i is Audio).Cast<Audio>();
-
         protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
         protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
         {
         {
             return Tracks;
             return Tracks;

+ 32 - 30
MediaBrowser.Controller/Entities/Audio/MusicArtist.cs

@@ -44,6 +44,36 @@ namespace MediaBrowser.Controller.Entities.Audio
         [JsonIgnore]
         [JsonIgnore]
         public override bool SupportsPlayedStatus => false;
         public override bool SupportsPlayedStatus => false;
 
 
+        /// <summary>
+        /// Gets the folder containing the item.
+        /// If the item is a folder, it returns the folder itself.
+        /// </summary>
+        /// <value>The containing folder path.</value>
+        [JsonIgnore]
+        public override string ContainingFolderPath => Path;
+
+        [JsonIgnore]
+        public override IEnumerable<BaseItem> Children
+        {
+            get
+            {
+                if (IsAccessedByName)
+                {
+                    return new List<BaseItem>();
+                }
+
+                return base.Children;
+            }
+        }
+
+        [JsonIgnore]
+        public override bool SupportsPeople => false;
+
+        public static string GetPath(string name)
+        {
+            return GetPath(name, true);
+        }
+
         public override double GetDefaultPrimaryImageAspectRatio()
         public override double GetDefaultPrimaryImageAspectRatio()
         {
         {
             return 1;
             return 1;
@@ -65,20 +95,6 @@ namespace MediaBrowser.Controller.Entities.Audio
             return LibraryManager.GetItemList(query);
             return LibraryManager.GetItemList(query);
         }
         }
 
 
-        [JsonIgnore]
-        public override IEnumerable<BaseItem> Children
-        {
-            get
-            {
-                if (IsAccessedByName)
-                {
-                    return new List<BaseItem>();
-                }
-
-                return base.Children;
-            }
-        }
-
         public override int GetChildCount(User user)
         public override int GetChildCount(User user)
         {
         {
             return IsAccessedByName ? 0 : base.GetChildCount(user);
             return IsAccessedByName ? 0 : base.GetChildCount(user);
@@ -113,14 +129,6 @@ namespace MediaBrowser.Controller.Entities.Audio
             return list;
             return list;
         }
         }
 
 
-        /// <summary>
-        /// Gets the folder containing the item.
-        /// If the item is a folder, it returns the folder itself.
-        /// </summary>
-        /// <value>The containing folder path.</value>
-        [JsonIgnore]
-        public override string ContainingFolderPath => Path;
-
         /// <summary>
         /// <summary>
         /// Gets the user data key.
         /// Gets the user data key.
         /// </summary>
         /// </summary>
@@ -167,14 +175,6 @@ namespace MediaBrowser.Controller.Entities.Audio
             return info;
             return info;
         }
         }
 
 
-        [JsonIgnore]
-        public override bool SupportsPeople => false;
-
-        public static string GetPath(string name)
-        {
-            return GetPath(name, true);
-        }
-
         public static string GetPath(string name, bool normalizeName)
         public static string GetPath(string name, bool normalizeName)
         {
         {
             // Trim the period at the end because windows will have a hard time with that
             // Trim the period at the end because windows will have a hard time with that
@@ -208,6 +208,8 @@ namespace MediaBrowser.Controller.Entities.Audio
         /// <summary>
         /// <summary>
         /// This is called before any metadata refresh and returns true or false indicating if changes were made.
         /// This is called before any metadata refresh and returns true or false indicating if changes were made.
         /// </summary>
         /// </summary>
+        /// <param name="replaceAllMetadata">Option to replace metadata.</param>
+        /// <returns>True if metadata changed.</returns>
         public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         {
         {
             var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
             var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);

+ 18 - 16
MediaBrowser.Controller/Entities/Audio/MusicGenre.cs

@@ -15,19 +15,6 @@ namespace MediaBrowser.Controller.Entities.Audio
     /// </summary>
     /// </summary>
     public class MusicGenre : BaseItem, IItemByName
     public class MusicGenre : BaseItem, IItemByName
     {
     {
-        public override List<string> GetUserDataKeys()
-        {
-            var list = base.GetUserDataKeys();
-
-            list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
-            return list;
-        }
-
-        public override string CreatePresentationUniqueKey()
-        {
-            return GetUserDataKeys()[0];
-        }
-
         [JsonIgnore]
         [JsonIgnore]
         public override bool SupportsAddingToPlaylist => true;
         public override bool SupportsAddingToPlaylist => true;
 
 
@@ -45,6 +32,22 @@ namespace MediaBrowser.Controller.Entities.Audio
         [JsonIgnore]
         [JsonIgnore]
         public override string ContainingFolderPath => Path;
         public override string ContainingFolderPath => Path;
 
 
+        [JsonIgnore]
+        public override bool SupportsPeople => false;
+
+        public override List<string> GetUserDataKeys()
+        {
+            var list = base.GetUserDataKeys();
+
+            list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
+            return list;
+        }
+
+        public override string CreatePresentationUniqueKey()
+        {
+            return GetUserDataKeys()[0];
+        }
+
         public override double GetDefaultPrimaryImageAspectRatio()
         public override double GetDefaultPrimaryImageAspectRatio()
         {
         {
             return 1;
             return 1;
@@ -60,9 +63,6 @@ namespace MediaBrowser.Controller.Entities.Audio
             return true;
             return true;
         }
         }
 
 
-        [JsonIgnore]
-        public override bool SupportsPeople => false;
-
         public IList<BaseItem> GetTaggedItems(InternalItemsQuery query)
         public IList<BaseItem> GetTaggedItems(InternalItemsQuery query)
         {
         {
             query.GenreIds = new[] { Id };
             query.GenreIds = new[] { Id };
@@ -106,6 +106,8 @@ namespace MediaBrowser.Controller.Entities.Audio
         /// <summary>
         /// <summary>
         /// This is called before any metadata refresh and returns true or false indicating if changes were made.
         /// This is called before any metadata refresh and returns true or false indicating if changes were made.
         /// </summary>
         /// </summary>
+        /// <param name="replaceAllMetadata">Option to replace metadata.</param>
+        /// <returns>True if metadata changed.</returns>
         public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         {
         {
             var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
             var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);

+ 1 - 1
MediaBrowser.Controller/Entities/AudioBook.cs

@@ -1,6 +1,6 @@
 #nullable disable
 #nullable disable
 
 
-#pragma warning disable CS1591
+#pragma warning disable CA1724, CS1591
 
 
 using System;
 using System;
 using System.Text.Json.Serialization;
 using System.Text.Json.Serialization;

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 523 - 381
MediaBrowser.Controller/Entities/BaseItem.cs


+ 5 - 0
MediaBrowser.Controller/Entities/BaseItemExtensions.cs

@@ -64,6 +64,8 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         /// </summary>
         /// <param name="source">The source object.</param>
         /// <param name="source">The source object.</param>
         /// <param name="dest">The destination object.</param>
         /// <param name="dest">The destination object.</param>
+        /// <typeparam name="T">Source type.</typeparam>
+        /// <typeparam name="TU">Destination type.</typeparam>
         public static void DeepCopy<T, TU>(this T source, TU dest)
         public static void DeepCopy<T, TU>(this T source, TU dest)
             where T : BaseItem
             where T : BaseItem
             where TU : BaseItem
             where TU : BaseItem
@@ -109,6 +111,9 @@ namespace MediaBrowser.Controller.Entities
         /// Copies all properties on newly created object. Skips properties that do not exist.
         /// Copies all properties on newly created object. Skips properties that do not exist.
         /// </summary>
         /// </summary>
         /// <param name="source">The source object.</param>
         /// <param name="source">The source object.</param>
+        /// <typeparam name="T">Source type.</typeparam>
+        /// <typeparam name="TU">Destination type.</typeparam>
+        /// <returns>Destination object.</returns>
         public static TU DeepCopy<T, TU>(this T source)
         public static TU DeepCopy<T, TU>(this T source)
             where T : BaseItem
             where T : BaseItem
             where TU : BaseItem, new()
             where TU : BaseItem, new()

+ 6 - 6
MediaBrowser.Controller/Entities/BasePluginFolder.cs

@@ -15,6 +15,12 @@ namespace MediaBrowser.Controller.Entities
         [JsonIgnore]
         [JsonIgnore]
         public virtual string CollectionType => null;
         public virtual string CollectionType => null;
 
 
+        [JsonIgnore]
+        public override bool SupportsInheritedParentImages => false;
+
+        [JsonIgnore]
+        public override bool SupportsPeople => false;
+
         public override bool CanDelete()
         public override bool CanDelete()
         {
         {
             return false;
             return false;
@@ -24,11 +30,5 @@ namespace MediaBrowser.Controller.Entities
         {
         {
             return true;
             return true;
         }
         }
-
-        [JsonIgnore]
-        public override bool SupportsInheritedParentImages => false;
-
-        [JsonIgnore]
-        public override bool SupportsPeople => false;
     }
     }
 }
 }

+ 20 - 20
MediaBrowser.Controller/Entities/CollectionFolder.cs

@@ -41,6 +41,23 @@ namespace MediaBrowser.Controller.Entities
             PhysicalFolderIds = Array.Empty<Guid>();
             PhysicalFolderIds = Array.Empty<Guid>();
         }
         }
 
 
+        /// <summary>
+        /// Gets the display preferences id.
+        /// </summary>
+        /// <remarks>
+        /// Allow different display preferences for each collection folder.
+        /// </remarks>
+        /// <value>The display prefs id.</value>
+        [JsonIgnore]
+        public override Guid DisplayPreferencesId => Id;
+
+        [JsonIgnore]
+        public override string[] PhysicalLocations => PhysicalLocationsList;
+
+        public string[] PhysicalLocationsList { get; set; }
+
+        public Guid[] PhysicalFolderIds { get; set; }
+
         public static IXmlSerializer XmlSerializer { get; set; }
         public static IXmlSerializer XmlSerializer { get; set; }
 
 
         public static IServerApplicationHost ApplicationHost { get; set; }
         public static IServerApplicationHost ApplicationHost { get; set; }
@@ -63,6 +80,9 @@ namespace MediaBrowser.Controller.Entities
         [JsonIgnore]
         [JsonIgnore]
         public override IEnumerable<BaseItem> Children => GetActualChildren();
         public override IEnumerable<BaseItem> Children => GetActualChildren();
 
 
+        [JsonIgnore]
+        public override bool SupportsPeople => false;
+
         public override bool CanDelete()
         public override bool CanDelete()
         {
         {
             return false;
             return false;
@@ -160,23 +180,6 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
-        /// <summary>
-        /// Gets the display preferences id.
-        /// </summary>
-        /// <remarks>
-        /// Allow different display preferences for each collection folder.
-        /// </remarks>
-        /// <value>The display prefs id.</value>
-        [JsonIgnore]
-        public override Guid DisplayPreferencesId => Id;
-
-        [JsonIgnore]
-        public override string[] PhysicalLocations => PhysicalLocationsList;
-
-        public string[] PhysicalLocationsList { get; set; }
-
-        public Guid[] PhysicalFolderIds { get; set; }
-
         public override bool IsSaveLocalMetadataEnabled()
         public override bool IsSaveLocalMetadataEnabled()
         {
         {
             return true;
             return true;
@@ -373,8 +376,5 @@ namespace MediaBrowser.Controller.Entities
 
 
             return result;
             return result;
         }
         }
-
-        [JsonIgnore]
-        public override bool SupportsPeople => false;
     }
     }
 }
 }

+ 2 - 0
MediaBrowser.Controller/Entities/Extensions.cs

@@ -15,6 +15,8 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// <summary>
         /// Adds the trailer URL.
         /// Adds the trailer URL.
         /// </summary>
         /// </summary>
+        /// <param name="item">Media item.</param>
+        /// <param name="url">Trailer URL.</param>
         public static void AddTrailerUrl(this BaseItem item, string url)
         public static void AddTrailerUrl(this BaseItem item, string url)
         {
         {
             if (string.IsNullOrEmpty(url))
             if (string.IsNullOrEmpty(url))

+ 6 - 5
MediaBrowser.Controller/Entities/Folder.cs

@@ -1,6 +1,6 @@
 #nullable disable
 #nullable disable
 
 
-#pragma warning disable CS1591
+#pragma warning disable CA1002, CA1721, CA1819, CS1591
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -165,6 +165,8 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
+        public static ICollectionManager CollectionManager { get; set; }
+
         public override bool CanDelete()
         public override bool CanDelete()
         {
         {
             if (IsRoot)
             if (IsRoot)
@@ -258,6 +260,7 @@ namespace MediaBrowser.Controller.Entities
         /// Loads our children.  Validation will occur externally.
         /// Loads our children.  Validation will occur externally.
         /// We want this synchronous.
         /// We want this synchronous.
         /// </summary>
         /// </summary>
+        /// <returns>Returns children.</returns>
         protected virtual List<BaseItem> LoadChildren()
         protected virtual List<BaseItem> LoadChildren()
         {
         {
             // logger.LogDebug("Loading children from {0} {1} {2}", GetType().Name, Id, Path);
             // logger.LogDebug("Loading children from {0} {1} {2}", GetType().Name, Id, Path);
@@ -642,6 +645,8 @@ namespace MediaBrowser.Controller.Entities
         /// Get the children of this folder from the actual file system.
         /// Get the children of this folder from the actual file system.
         /// </summary>
         /// </summary>
         /// <returns>IEnumerable{BaseItem}.</returns>
         /// <returns>IEnumerable{BaseItem}.</returns>
+        /// <param name="directoryService">The directory service to use for operation.</param>
+        /// <returns>Returns set of base items.</returns>
         protected virtual IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
         protected virtual IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
         {
         {
             var collectionType = LibraryManager.GetContentType(this);
             var collectionType = LibraryManager.GetContentType(this);
@@ -998,8 +1003,6 @@ namespace MediaBrowser.Controller.Entities
             return PostFilterAndSort(items, query, true);
             return PostFilterAndSort(items, query, true);
         }
         }
 
 
-        public static ICollectionManager CollectionManager { get; set; }
-
         protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query, bool enableSorting)
         protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query, bool enableSorting)
         {
         {
             var user = query.User;
             var user = query.User;
@@ -1666,7 +1669,6 @@ namespace MediaBrowser.Controller.Entities
         /// <param name="user">The user.</param>
         /// <param name="user">The user.</param>
         /// <param name="datePlayed">The date played.</param>
         /// <param name="datePlayed">The date played.</param>
         /// <param name="resetPosition">if set to <c>true</c> [reset position].</param>
         /// <param name="resetPosition">if set to <c>true</c> [reset position].</param>
-        /// <returns>Task.</returns>
         public override void MarkPlayed(
         public override void MarkPlayed(
             User user,
             User user,
             DateTime? datePlayed,
             DateTime? datePlayed,
@@ -1708,7 +1710,6 @@ namespace MediaBrowser.Controller.Entities
         /// Marks the unplayed.
         /// Marks the unplayed.
         /// </summary>
         /// </summary>
         /// <param name="user">The user.</param>
         /// <param name="user">The user.</param>
-        /// <returns>Task.</returns>
         public override void MarkUnplayed(User user)
         public override void MarkUnplayed(User user)
         {
         {
             var itemsResult = GetItemList(new InternalItemsQuery
             var itemsResult = GetItemList(new InternalItemsQuery

+ 1 - 1
MediaBrowser.Controller/Entities/ICollectionFolder.cs

@@ -1,6 +1,6 @@
 #nullable disable
 #nullable disable
 
 
-#pragma warning disable CS1591
+#pragma warning disable CA1819, CS1591
 
 
 using System;
 using System;
 
 

+ 2 - 0
MediaBrowser.Controller/Entities/IHasMediaSources.cs

@@ -20,6 +20,8 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// <summary>
         /// Gets the media sources.
         /// Gets the media sources.
         /// </summary>
         /// </summary>
+        /// <param name="enablePathSubstitution"><c>true</c> to enable path substitution, <c>false</c> to not.</param>
+        /// <returns>A list of media sources.</returns>
         List<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution);
         List<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution);
 
 
         List<MediaStream> GetMediaStreams();
         List<MediaStream> GetMediaStreams();

+ 1 - 1
MediaBrowser.Controller/Entities/IHasShares.cs

@@ -1,6 +1,6 @@
 #nullable disable
 #nullable disable
 
 
-#pragma warning disable CS1591
+#pragma warning disable CA1819, CS1591
 
 
 namespace MediaBrowser.Controller.Entities
 namespace MediaBrowser.Controller.Entities
 {
 {

+ 3 - 0
MediaBrowser.Controller/Entities/IHasTrailers.cs

@@ -39,6 +39,7 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// <summary>
         /// Gets the trailer count.
         /// Gets the trailer count.
         /// </summary>
         /// </summary>
+        /// <param name="item">Media item.</param>
         /// <returns><see cref="IReadOnlyList{Guid}" />.</returns>
         /// <returns><see cref="IReadOnlyList{Guid}" />.</returns>
         public static int GetTrailerCount(this IHasTrailers item)
         public static int GetTrailerCount(this IHasTrailers item)
             => item.LocalTrailerIds.Count + item.RemoteTrailerIds.Count;
             => item.LocalTrailerIds.Count + item.RemoteTrailerIds.Count;
@@ -46,6 +47,7 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// <summary>
         /// Gets the trailer ids.
         /// Gets the trailer ids.
         /// </summary>
         /// </summary>
+        /// <param name="item">Media item.</param>
         /// <returns><see cref="IReadOnlyList{Guid}" />.</returns>
         /// <returns><see cref="IReadOnlyList{Guid}" />.</returns>
         public static IReadOnlyList<Guid> GetTrailerIds(this IHasTrailers item)
         public static IReadOnlyList<Guid> GetTrailerIds(this IHasTrailers item)
         {
         {
@@ -70,6 +72,7 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// <summary>
         /// Gets the trailers.
         /// Gets the trailers.
         /// </summary>
         /// </summary>
+        /// <param name="item">Media item.</param>
         /// <returns><see cref="IReadOnlyList{BaseItem}" />.</returns>
         /// <returns><see cref="IReadOnlyList{BaseItem}" />.</returns>
         public static IReadOnlyList<BaseItem> GetTrailers(this IHasTrailers item)
         public static IReadOnlyList<BaseItem> GetTrailers(this IHasTrailers item)
         {
         {

+ 79 - 79
MediaBrowser.Controller/Entities/InternalItemsQuery.cs

@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CA1044, CA1819, CA2227, CS1591
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -12,6 +12,55 @@ namespace MediaBrowser.Controller.Entities
 {
 {
     public class InternalItemsQuery
     public class InternalItemsQuery
     {
     {
+        public InternalItemsQuery()
+        {
+            AlbumArtistIds = Array.Empty<Guid>();
+            AlbumIds = Array.Empty<Guid>();
+            AncestorIds = Array.Empty<Guid>();
+            ArtistIds = Array.Empty<Guid>();
+            BlockUnratedItems = Array.Empty<UnratedItem>();
+            BoxSetLibraryFolders = Array.Empty<Guid>();
+            ChannelIds = Array.Empty<Guid>();
+            ContributingArtistIds = Array.Empty<Guid>();
+            DtoOptions = new DtoOptions();
+            EnableTotalRecordCount = true;
+            ExcludeArtistIds = Array.Empty<Guid>();
+            ExcludeInheritedTags = Array.Empty<string>();
+            ExcludeItemIds = Array.Empty<Guid>();
+            ExcludeItemTypes = Array.Empty<string>();
+            ExcludeTags = Array.Empty<string>();
+            GenreIds = Array.Empty<Guid>();
+            Genres = Array.Empty<string>();
+            GroupByPresentationUniqueKey = true;
+            ImageTypes = Array.Empty<ImageType>();
+            IncludeItemTypes = Array.Empty<string>();
+            ItemIds = Array.Empty<Guid>();
+            MediaTypes = Array.Empty<string>();
+            MinSimilarityScore = 20;
+            OfficialRatings = Array.Empty<string>();
+            OrderBy = Array.Empty<ValueTuple<string, SortOrder>>();
+            PersonIds = Array.Empty<Guid>();
+            PersonTypes = Array.Empty<string>();
+            PresetViews = Array.Empty<string>();
+            SeriesStatuses = Array.Empty<SeriesStatus>();
+            SourceTypes = Array.Empty<SourceType>();
+            StudioIds = Array.Empty<Guid>();
+            Tags = Array.Empty<string>();
+            TopParentIds = Array.Empty<Guid>();
+            TrailerTypes = Array.Empty<TrailerType>();
+            VideoTypes = Array.Empty<VideoType>();
+            Years = Array.Empty<int>();
+        }
+
+        public InternalItemsQuery(User? user)
+            : this()
+        {
+            if (user != null)
+            {
+                SetUser(user);
+            }
+        }
+
         public bool Recursive { get; set; }
         public bool Recursive { get; set; }
 
 
         public int? StartIndex { get; set; }
         public int? StartIndex { get; set; }
@@ -186,23 +235,6 @@ namespace MediaBrowser.Controller.Entities
 
 
         public Guid[] TopParentIds { get; set; }
         public Guid[] TopParentIds { get; set; }
 
 
-        public BaseItem? Parent
-        {
-            set
-            {
-                if (value == null)
-                {
-                    ParentId = Guid.Empty;
-                    ParentType = null;
-                }
-                else
-                {
-                    ParentId = value.Id;
-                    ParentType = value.GetType().Name;
-                }
-            }
-        }
-
         public string[] PresetViews { get; set; }
         public string[] PresetViews { get; set; }
 
 
         public TrailerType[] TrailerTypes { get; set; }
         public TrailerType[] TrailerTypes { get; set; }
@@ -270,70 +302,21 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         /// </summary>
         public bool? DisplayAlbumFolders { get; set; }
         public bool? DisplayAlbumFolders { get; set; }
 
 
-        public InternalItemsQuery()
-        {
-            AlbumArtistIds = Array.Empty<Guid>();
-            AlbumIds = Array.Empty<Guid>();
-            AncestorIds = Array.Empty<Guid>();
-            ArtistIds = Array.Empty<Guid>();
-            BlockUnratedItems = Array.Empty<UnratedItem>();
-            BoxSetLibraryFolders = Array.Empty<Guid>();
-            ChannelIds = Array.Empty<Guid>();
-            ContributingArtistIds = Array.Empty<Guid>();
-            DtoOptions = new DtoOptions();
-            EnableTotalRecordCount = true;
-            ExcludeArtistIds = Array.Empty<Guid>();
-            ExcludeInheritedTags = Array.Empty<string>();
-            ExcludeItemIds = Array.Empty<Guid>();
-            ExcludeItemTypes = Array.Empty<string>();
-            ExcludeTags = Array.Empty<string>();
-            GenreIds = Array.Empty<Guid>();
-            Genres = Array.Empty<string>();
-            GroupByPresentationUniqueKey = true;
-            ImageTypes = Array.Empty<ImageType>();
-            IncludeItemTypes = Array.Empty<string>();
-            ItemIds = Array.Empty<Guid>();
-            MediaTypes = Array.Empty<string>();
-            MinSimilarityScore = 20;
-            OfficialRatings = Array.Empty<string>();
-            OrderBy = Array.Empty<ValueTuple<string, SortOrder>>();
-            PersonIds = Array.Empty<Guid>();
-            PersonTypes = Array.Empty<string>();
-            PresetViews = Array.Empty<string>();
-            SeriesStatuses = Array.Empty<SeriesStatus>();
-            SourceTypes = Array.Empty<SourceType>();
-            StudioIds = Array.Empty<Guid>();
-            Tags = Array.Empty<string>();
-            TopParentIds = Array.Empty<Guid>();
-            TrailerTypes = Array.Empty<TrailerType>();
-            VideoTypes = Array.Empty<VideoType>();
-            Years = Array.Empty<int>();
-        }
-
-        public InternalItemsQuery(User? user)
-            : this()
-        {
-            if (user != null)
-            {
-                SetUser(user);
-            }
-        }
-
-        public void SetUser(User user)
+        public BaseItem? Parent
         {
         {
-            MaxParentalRating = user.MaxParentalAgeRating;
-
-            if (MaxParentalRating.HasValue)
+            set
             {
             {
-                string other = UnratedItem.Other.ToString();
-                BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems)
-                    .Where(i => i != other)
-                    .Select(e => Enum.Parse<UnratedItem>(e, true)).ToArray();
+                if (value == null)
+                {
+                    ParentId = Guid.Empty;
+                    ParentType = null;
+                }
+                else
+                {
+                    ParentId = value.Id;
+                    ParentType = value.GetType().Name;
+                }
             }
             }
-
-            ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags);
-
-            User = user;
         }
         }
 
 
         public Dictionary<string, string>? HasAnyProviderId { get; set; }
         public Dictionary<string, string>? HasAnyProviderId { get; set; }
@@ -361,5 +344,22 @@ namespace MediaBrowser.Controller.Entities
         public string? SearchTerm { get; set; }
         public string? SearchTerm { get; set; }
 
 
         public string? SeriesTimerId { get; set; }
         public string? SeriesTimerId { get; set; }
+
+        public void SetUser(User user)
+        {
+            MaxParentalRating = user.MaxParentalAgeRating;
+
+            if (MaxParentalRating.HasValue)
+            {
+                string other = UnratedItem.Other.ToString();
+                BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems)
+                    .Where(i => i != other)
+                    .Select(e => Enum.Parse<UnratedItem>(e, true)).ToArray();
+            }
+
+            ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags);
+
+            User = user;
+        }
     }
     }
 }
 }

+ 25 - 25
MediaBrowser.Controller/Entities/Movies/BoxSet.cs

@@ -1,6 +1,6 @@
 #nullable disable
 #nullable disable
 
 
-#pragma warning disable CS1591
+#pragma warning disable CA1721, CA1819, CS1591
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -49,6 +49,30 @@ namespace MediaBrowser.Controller.Entities.Movies
         /// <value>The display order.</value>
         /// <value>The display order.</value>
         public string DisplayOrder { get; set; }
         public string DisplayOrder { get; set; }
 
 
+        [JsonIgnore]
+        private bool IsLegacyBoxSet
+        {
+            get
+            {
+                if (string.IsNullOrEmpty(Path))
+                {
+                    return false;
+                }
+
+                if (LinkedChildren.Length > 0)
+                {
+                    return false;
+                }
+
+                return !FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, Path);
+            }
+        }
+
+        [JsonIgnore]
+        public override bool IsPreSorted => true;
+
+        public Guid[] LibraryFolderIds { get; set; }
+
         protected override bool GetBlockUnratedValue(User user)
         protected override bool GetBlockUnratedValue(User user)
         {
         {
             return user.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Movie);
             return user.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Movie);
@@ -83,28 +107,6 @@ namespace MediaBrowser.Controller.Entities.Movies
             return new List<BaseItem>();
             return new List<BaseItem>();
         }
         }
 
 
-        [JsonIgnore]
-        private bool IsLegacyBoxSet
-        {
-            get
-            {
-                if (string.IsNullOrEmpty(Path))
-                {
-                    return false;
-                }
-
-                if (LinkedChildren.Length > 0)
-                {
-                    return false;
-                }
-
-                return !FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, Path);
-            }
-        }
-
-        [JsonIgnore]
-        public override bool IsPreSorted => true;
-
         public override bool IsAuthorizedToDelete(User user, List<Folder> allCollectionFolders)
         public override bool IsAuthorizedToDelete(User user, List<Folder> allCollectionFolders)
         {
         {
             return true;
             return true;
@@ -191,8 +193,6 @@ namespace MediaBrowser.Controller.Entities.Movies
             return IsVisible(user);
             return IsVisible(user);
         }
         }
 
 
-        public Guid[] LibraryFolderIds { get; set; }
-
         private Guid[] GetLibraryFolderIds(User user)
         private Guid[] GetLibraryFolderIds(User user)
         {
         {
             return LibraryManager.GetUserRootFolder().GetChildren(user, true)
             return LibraryManager.GetUserRootFolder().GetChildren(user, true)

+ 22 - 20
MediaBrowser.Controller/Entities/Person.cs

@@ -16,6 +16,26 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     /// </summary>
     public class Person : BaseItem, IItemByName, IHasLookupInfo<PersonLookupInfo>
     public class Person : BaseItem, IItemByName, IHasLookupInfo<PersonLookupInfo>
     {
     {
+        /// <summary>
+        /// Gets the folder containing the item.
+        /// If the item is a folder, it returns the folder itself.
+        /// </summary>
+        /// <value>The containing folder path.</value>
+        [JsonIgnore]
+        public override string ContainingFolderPath => Path;
+
+        /// <summary>
+        /// Gets a value indicating whether to enable alpha numeric sorting.
+        /// </summary>
+        [JsonIgnore]
+        public override bool EnableAlphaNumericSorting => false;
+
+        [JsonIgnore]
+        public override bool SupportsPeople => false;
+
+        [JsonIgnore]
+        public override bool SupportsAncestors => false;
+
         public override List<string> GetUserDataKeys()
         public override List<string> GetUserDataKeys()
         {
         {
             var list = base.GetUserDataKeys();
             var list = base.GetUserDataKeys();
@@ -49,14 +69,6 @@ namespace MediaBrowser.Controller.Entities
             return LibraryManager.GetItemList(query);
             return LibraryManager.GetItemList(query);
         }
         }
 
 
-        /// <summary>
-        /// Gets the folder containing the item.
-        /// If the item is a folder, it returns the folder itself.
-        /// </summary>
-        /// <value>The containing folder path.</value>
-        [JsonIgnore]
-        public override string ContainingFolderPath => Path;
-
         public override bool CanDelete()
         public override bool CanDelete()
         {
         {
             return false;
             return false;
@@ -67,18 +79,6 @@ namespace MediaBrowser.Controller.Entities
             return true;
             return true;
         }
         }
 
 
-        /// <summary>
-        /// Gets a value indicating whether to enable alpha numeric sorting.
-        /// </summary>
-        [JsonIgnore]
-        public override bool EnableAlphaNumericSorting => false;
-
-        [JsonIgnore]
-        public override bool SupportsPeople => false;
-
-        [JsonIgnore]
-        public override bool SupportsAncestors => false;
-
         public static string GetPath(string name)
         public static string GetPath(string name)
         {
         {
             return GetPath(name, true);
             return GetPath(name, true);
@@ -129,6 +129,8 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// <summary>
         /// This is called before any metadata refresh and returns true or false indicating if changes were made.
         /// This is called before any metadata refresh and returns true or false indicating if changes were made.
         /// </summary>
         /// </summary>
+        /// <param name="replaceAllMetadata"><c>true</c> to replace all metadata, <c>false</c> to not.</param>
+        /// <returns><c>true</c> if changes were made, <c>false</c> if not.</returns>
         public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         {
         {
             var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
             var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);

+ 1 - 1
MediaBrowser.Controller/Entities/PersonInfo.cs

@@ -1,6 +1,6 @@
 #nullable disable
 #nullable disable
 
 
-#pragma warning disable CS1591
+#pragma warning disable CA2227, CS1591
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;

+ 24 - 24
MediaBrowser.Controller/Entities/Photo.cs

@@ -36,6 +36,30 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
+        public string CameraMake { get; set; }
+
+        public string CameraModel { get; set; }
+
+        public string Software { get; set; }
+
+        public double? ExposureTime { get; set; }
+
+        public double? FocalLength { get; set; }
+
+        public ImageOrientation? Orientation { get; set; }
+
+        public double? Aperture { get; set; }
+
+        public double? ShutterSpeed { get; set; }
+
+        public double? Latitude { get; set; }
+
+        public double? Longitude { get; set; }
+
+        public double? Altitude { get; set; }
+
+        public int? IsoSpeedRating { get; set; }
+
         public override bool CanDownload()
         public override bool CanDownload()
         {
         {
             return true;
             return true;
@@ -69,29 +93,5 @@ namespace MediaBrowser.Controller.Entities
 
 
             return base.GetDefaultPrimaryImageAspectRatio();
             return base.GetDefaultPrimaryImageAspectRatio();
         }
         }
-
-        public string CameraMake { get; set; }
-
-        public string CameraModel { get; set; }
-
-        public string Software { get; set; }
-
-        public double? ExposureTime { get; set; }
-
-        public double? FocalLength { get; set; }
-
-        public ImageOrientation? Orientation { get; set; }
-
-        public double? Aperture { get; set; }
-
-        public double? ShutterSpeed { get; set; }
-
-        public double? Latitude { get; set; }
-
-        public double? Longitude { get; set; }
-
-        public double? Altitude { get; set; }
-
-        public int? IsoSpeedRating { get; set; }
     }
     }
 }
 }

+ 18 - 16
MediaBrowser.Controller/Entities/Studio.cs

@@ -15,19 +15,6 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     /// </summary>
     public class Studio : BaseItem, IItemByName
     public class Studio : BaseItem, IItemByName
     {
     {
-        public override List<string> GetUserDataKeys()
-        {
-            var list = base.GetUserDataKeys();
-
-            list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
-            return list;
-        }
-
-        public override string CreatePresentationUniqueKey()
-        {
-            return GetUserDataKeys()[0];
-        }
-
         /// <summary>
         /// <summary>
         /// Gets the folder containing the item.
         /// Gets the folder containing the item.
         /// If the item is a folder, it returns the folder itself.
         /// If the item is a folder, it returns the folder itself.
@@ -42,6 +29,22 @@ namespace MediaBrowser.Controller.Entities
         [JsonIgnore]
         [JsonIgnore]
         public override bool SupportsAncestors => false;
         public override bool SupportsAncestors => false;
 
 
+        [JsonIgnore]
+        public override bool SupportsPeople => false;
+
+        public override List<string> GetUserDataKeys()
+        {
+            var list = base.GetUserDataKeys();
+
+            list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
+            return list;
+        }
+
+        public override string CreatePresentationUniqueKey()
+        {
+            return GetUserDataKeys()[0];
+        }
+
         public override double GetDefaultPrimaryImageAspectRatio()
         public override double GetDefaultPrimaryImageAspectRatio()
         {
         {
             double value = 16;
             double value = 16;
@@ -67,9 +70,6 @@ namespace MediaBrowser.Controller.Entities
             return LibraryManager.GetItemList(query);
             return LibraryManager.GetItemList(query);
         }
         }
 
 
-        [JsonIgnore]
-        public override bool SupportsPeople => false;
-
         public static string GetPath(string name)
         public static string GetPath(string name)
         {
         {
             return GetPath(name, true);
             return GetPath(name, true);
@@ -105,6 +105,8 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// <summary>
         /// This is called before any metadata refresh and returns true or false indicating if changes were made.
         /// This is called before any metadata refresh and returns true or false indicating if changes were made.
         /// </summary>
         /// </summary>
+        /// <param name="replaceAllMetadata"><c>true</c> to replace all metadata, <c>false</c> to not.</param>
+        /// <returns><c>true</c> if changes were made, <c>false</c> if not.</returns>
         public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         {
         {
             var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
             var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);

+ 68 - 68
MediaBrowser.Controller/Entities/TV/Episode.cs

@@ -49,12 +49,6 @@ namespace MediaBrowser.Controller.Entities.TV
         /// <value>The index number.</value>
         /// <value>The index number.</value>
         public int? IndexNumberEnd { get; set; }
         public int? IndexNumberEnd { get; set; }
 
 
-        public string FindSeriesSortName()
-        {
-            var series = Series;
-            return series == null ? SeriesName : series.SortName;
-        }
-
         [JsonIgnore]
         [JsonIgnore]
         protected override bool SupportsOwnedItems => IsStacked || MediaSourceCount > 1;
         protected override bool SupportsOwnedItems => IsStacked || MediaSourceCount > 1;
 
 
@@ -76,45 +70,6 @@ namespace MediaBrowser.Controller.Entities.TV
         [JsonIgnore]
         [JsonIgnore]
         protected override bool EnableDefaultVideoUserDataKeys => false;
         protected override bool EnableDefaultVideoUserDataKeys => false;
 
 
-        public override double GetDefaultPrimaryImageAspectRatio()
-        {
-            // hack for tv plugins
-            if (SourceType == SourceType.Channel)
-            {
-                return 0;
-            }
-
-            return 16.0 / 9;
-        }
-
-        public override List<string> GetUserDataKeys()
-        {
-            var list = base.GetUserDataKeys();
-
-            var series = Series;
-            if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue)
-            {
-                var seriesUserDataKeys = series.GetUserDataKeys();
-                var take = seriesUserDataKeys.Count;
-                if (seriesUserDataKeys.Count > 1)
-                {
-                    take--;
-                }
-
-                var newList = seriesUserDataKeys.GetRange(0, take);
-                var suffix = ParentIndexNumber.Value.ToString("000", CultureInfo.InvariantCulture) + IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture);
-                for (int i = 0; i < take; i++)
-                {
-                    newList[i] = newList[i] + suffix;
-                }
-
-                newList.AddRange(list);
-                list = newList;
-            }
-
-            return list;
-        }
-
         /// <summary>
         /// <summary>
         /// Gets the Episode's Series Instance.
         /// Gets the Episode's Series Instance.
         /// </summary>
         /// </summary>
@@ -161,6 +116,74 @@ namespace MediaBrowser.Controller.Entities.TV
         [JsonIgnore]
         [JsonIgnore]
         public string SeasonName { get; set; }
         public string SeasonName { get; set; }
 
 
+        [JsonIgnore]
+        public override bool SupportsRemoteImageDownloading
+        {
+            get
+            {
+                if (IsMissingEpisode)
+                {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+
+        [JsonIgnore]
+        public bool IsMissingEpisode => LocationType == LocationType.Virtual;
+
+        [JsonIgnore]
+        public Guid SeasonId { get; set; }
+
+        [JsonIgnore]
+        public Guid SeriesId { get; set; }
+
+        public string FindSeriesSortName()
+        {
+            var series = Series;
+            return series == null ? SeriesName : series.SortName;
+        }
+
+        public override double GetDefaultPrimaryImageAspectRatio()
+        {
+            // hack for tv plugins
+            if (SourceType == SourceType.Channel)
+            {
+                return 0;
+            }
+
+            return 16.0 / 9;
+        }
+
+        public override List<string> GetUserDataKeys()
+        {
+            var list = base.GetUserDataKeys();
+
+            var series = Series;
+            if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue)
+            {
+                var seriesUserDataKeys = series.GetUserDataKeys();
+                var take = seriesUserDataKeys.Count;
+                if (seriesUserDataKeys.Count > 1)
+                {
+                    take--;
+                }
+
+                var newList = seriesUserDataKeys.GetRange(0, take);
+                var suffix = ParentIndexNumber.Value.ToString("000", CultureInfo.InvariantCulture) + IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture);
+                for (int i = 0; i < take; i++)
+                {
+                    newList[i] = newList[i] + suffix;
+                }
+
+                newList.AddRange(list);
+                list = newList;
+            }
+
+            return list;
+        }
+
         public string FindSeriesPresentationUniqueKey()
         public string FindSeriesPresentationUniqueKey()
         {
         {
             var series = Series;
             var series = Series;
@@ -242,29 +265,6 @@ namespace MediaBrowser.Controller.Entities.TV
             return false;
             return false;
         }
         }
 
 
-        [JsonIgnore]
-        public override bool SupportsRemoteImageDownloading
-        {
-            get
-            {
-                if (IsMissingEpisode)
-                {
-                    return false;
-                }
-
-                return true;
-            }
-        }
-
-        [JsonIgnore]
-        public bool IsMissingEpisode => LocationType == LocationType.Virtual;
-
-        [JsonIgnore]
-        public Guid SeasonId { get; set; }
-
-        [JsonIgnore]
-        public Guid SeriesId { get; set; }
-
         public Guid FindSeriesId()
         public Guid FindSeriesId()
         {
         {
             var series = FindParent<Series>();
             var series = FindParent<Series>();

+ 48 - 44
MediaBrowser.Controller/Entities/TV/Season.cs

@@ -38,6 +38,50 @@ namespace MediaBrowser.Controller.Entities.TV
         [JsonIgnore]
         [JsonIgnore]
         public override Guid DisplayParentId => SeriesId;
         public override Guid DisplayParentId => SeriesId;
 
 
+        /// <summary>
+        /// Gets this Episode's Series Instance.
+        /// </summary>
+        /// <value>The series.</value>
+        [JsonIgnore]
+        public Series Series
+        {
+            get
+            {
+                var seriesId = SeriesId;
+                if (seriesId == Guid.Empty)
+                {
+                    seriesId = FindSeriesId();
+                }
+
+                return seriesId == Guid.Empty ? null : (LibraryManager.GetItemById(seriesId) as Series);
+            }
+        }
+
+        [JsonIgnore]
+        public string SeriesPath
+        {
+            get
+            {
+                var series = Series;
+
+                if (series != null)
+                {
+                    return series.Path;
+                }
+
+                return System.IO.Path.GetDirectoryName(Path);
+            }
+        }
+
+        [JsonIgnore]
+        public string SeriesPresentationUniqueKey { get; set; }
+
+        [JsonIgnore]
+        public string SeriesName { get; set; }
+
+        [JsonIgnore]
+        public Guid SeriesId { get; set; }
+
         public override double GetDefaultPrimaryImageAspectRatio()
         public override double GetDefaultPrimaryImageAspectRatio()
         {
         {
             double value = 2;
             double value = 2;
@@ -80,41 +124,6 @@ namespace MediaBrowser.Controller.Entities.TV
             return result;
             return result;
         }
         }
 
 
-        /// <summary>
-        /// Gets this Episode's Series Instance.
-        /// </summary>
-        /// <value>The series.</value>
-        [JsonIgnore]
-        public Series Series
-        {
-            get
-            {
-                var seriesId = SeriesId;
-                if (seriesId == Guid.Empty)
-                {
-                    seriesId = FindSeriesId();
-                }
-
-                return seriesId == Guid.Empty ? null : (LibraryManager.GetItemById(seriesId) as Series);
-            }
-        }
-
-        [JsonIgnore]
-        public string SeriesPath
-        {
-            get
-            {
-                var series = Series;
-
-                if (series != null)
-                {
-                    return series.Path;
-                }
-
-                return System.IO.Path.GetDirectoryName(Path);
-            }
-        }
-
         public override string CreatePresentationUniqueKey()
         public override string CreatePresentationUniqueKey()
         {
         {
             if (IndexNumber.HasValue)
             if (IndexNumber.HasValue)
@@ -157,6 +166,9 @@ namespace MediaBrowser.Controller.Entities.TV
         /// <summary>
         /// <summary>
         /// Gets the episodes.
         /// Gets the episodes.
         /// </summary>
         /// </summary>
+        /// <param name="user">The user.</param>
+        /// <param name="options">The options to use.</param>
+        /// <returns>Set of episodes.</returns>
         public List<BaseItem> GetEpisodes(User user, DtoOptions options)
         public List<BaseItem> GetEpisodes(User user, DtoOptions options)
         {
         {
             return GetEpisodes(Series, user, options);
             return GetEpisodes(Series, user, options);
@@ -193,15 +205,6 @@ namespace MediaBrowser.Controller.Entities.TV
             return UnratedItem.Series;
             return UnratedItem.Series;
         }
         }
 
 
-        [JsonIgnore]
-        public string SeriesPresentationUniqueKey { get; set; }
-
-        [JsonIgnore]
-        public string SeriesName { get; set; }
-
-        [JsonIgnore]
-        public Guid SeriesId { get; set; }
-
         public string FindSeriesPresentationUniqueKey()
         public string FindSeriesPresentationUniqueKey()
         {
         {
             var series = Series;
             var series = Series;
@@ -241,6 +244,7 @@ namespace MediaBrowser.Controller.Entities.TV
         /// <summary>
         /// <summary>
         /// This is called before any metadata refresh and returns true or false indicating if changes were made.
         /// This is called before any metadata refresh and returns true or false indicating if changes were made.
         /// </summary>
         /// </summary>
+        /// <param name="replaceAllMetadata"><c>true</c> to replace metdata, <c>false</c> to not.</param>
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
         public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         {
         {

+ 11 - 3
MediaBrowser.Controller/Entities/TV/Series.cs

@@ -72,6 +72,9 @@ namespace MediaBrowser.Controller.Entities.TV
         /// <value>The status.</value>
         /// <value>The status.</value>
         public SeriesStatus? Status { get; set; }
         public SeriesStatus? Status { get; set; }
 
 
+        [JsonIgnore]
+        public override bool StopRefreshIfLocalMetadataFound => false;
+
         public override double GetDefaultPrimaryImageAspectRatio()
         public override double GetDefaultPrimaryImageAspectRatio()
         {
         {
             double value = 2;
             double value = 2;
@@ -394,6 +397,10 @@ namespace MediaBrowser.Controller.Entities.TV
         /// <summary>
         /// <summary>
         /// Filters the episodes by season.
         /// Filters the episodes by season.
         /// </summary>
         /// </summary>
+        /// <param name="episodes">The episodes.</param>
+        /// <param name="parentSeason">The season.</param>
+        /// <param name="includeSpecials"><c>true</c> to include special, <c>false</c> to not.</param>
+        /// <returns>The set of episodes.</returns>
         public static IEnumerable<BaseItem> FilterEpisodesBySeason(IEnumerable<BaseItem> episodes, Season parentSeason, bool includeSpecials)
         public static IEnumerable<BaseItem> FilterEpisodesBySeason(IEnumerable<BaseItem> episodes, Season parentSeason, bool includeSpecials)
         {
         {
             var seasonNumber = parentSeason.IndexNumber;
             var seasonNumber = parentSeason.IndexNumber;
@@ -424,6 +431,10 @@ namespace MediaBrowser.Controller.Entities.TV
         /// <summary>
         /// <summary>
         /// Filters the episodes by season.
         /// Filters the episodes by season.
         /// </summary>
         /// </summary>
+        /// <param name="episodes">The episodes.</param>
+        /// <param name="seasonNumber">The season.</param>
+        /// <param name="includeSpecials"><c>true</c> to include special, <c>false</c> to not.</param>
+        /// <returns>The set of episodes.</returns>
         public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool includeSpecials)
         public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool includeSpecials)
         {
         {
             if (!includeSpecials || seasonNumber < 1)
             if (!includeSpecials || seasonNumber < 1)
@@ -499,8 +510,5 @@ namespace MediaBrowser.Controller.Entities.TV
 
 
             return list;
             return list;
         }
         }
-
-        [JsonIgnore]
-        public override bool StopRefreshIfLocalMetadataFound => false;
     }
     }
 }
 }

+ 4 - 4
MediaBrowser.Controller/Entities/Trailer.cs

@@ -1,6 +1,6 @@
 #nullable disable
 #nullable disable
 
 
-#pragma warning disable CS1591
+#pragma warning disable CA1819, CS1591
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -23,6 +23,9 @@ namespace MediaBrowser.Controller.Entities
             TrailerTypes = Array.Empty<TrailerType>();
             TrailerTypes = Array.Empty<TrailerType>();
         }
         }
 
 
+        [JsonIgnore]
+        public override bool StopRefreshIfLocalMetadataFound => false;
+
         public TrailerType[] TrailerTypes { get; set; }
         public TrailerType[] TrailerTypes { get; set; }
 
 
         public override double GetDefaultPrimaryImageAspectRatio()
         public override double GetDefaultPrimaryImageAspectRatio()
@@ -97,8 +100,5 @@ namespace MediaBrowser.Controller.Entities
 
 
             return list;
             return list;
         }
         }
-
-        [JsonIgnore]
-        public override bool StopRefreshIfLocalMetadataFound => false;
     }
     }
 }
 }

+ 7 - 7
MediaBrowser.Controller/Entities/UserItemData.cs

@@ -12,6 +12,13 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     /// </summary>
     public class UserItemData
     public class UserItemData
     {
     {
+        public const double MinLikeValue = 6.5;
+
+        /// <summary>
+        /// The _rating.
+        /// </summary>
+        private double? _rating;
+
         /// <summary>
         /// <summary>
         /// Gets or sets the user id.
         /// Gets or sets the user id.
         /// </summary>
         /// </summary>
@@ -24,11 +31,6 @@ namespace MediaBrowser.Controller.Entities
         /// <value>The key.</value>
         /// <value>The key.</value>
         public string Key { get; set; }
         public string Key { get; set; }
 
 
-        /// <summary>
-        /// The _rating.
-        /// </summary>
-        private double? _rating;
-
         /// <summary>
         /// <summary>
         /// Gets or sets the users 0-10 rating.
         /// Gets or sets the users 0-10 rating.
         /// </summary>
         /// </summary>
@@ -93,8 +95,6 @@ namespace MediaBrowser.Controller.Entities
         /// <value>The index of the subtitle stream.</value>
         /// <value>The index of the subtitle stream.</value>
         public int? SubtitleStreamIndex { get; set; }
         public int? SubtitleStreamIndex { get; set; }
 
 
-        public const double MinLikeValue = 6.5;
-
         /// <summary>
         /// <summary>
         /// Gets or sets a value indicating whether the item is liked or not.
         /// Gets or sets a value indicating whether the item is liked or not.
         /// This should never be serialized.
         /// This should never be serialized.

+ 21 - 21
MediaBrowser.Controller/Entities/UserRootFolder.cs

@@ -21,8 +21,28 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     /// </summary>
     public class UserRootFolder : Folder
     public class UserRootFolder : Folder
     {
     {
-        private List<Guid> _childrenIds = null;
         private readonly object _childIdsLock = new object();
         private readonly object _childIdsLock = new object();
+        private List<Guid> _childrenIds = null;
+
+        [JsonIgnore]
+        public override bool SupportsInheritedParentImages => false;
+
+        [JsonIgnore]
+        public override bool SupportsPlayedStatus => false;
+
+        [JsonIgnore]
+        protected override bool SupportsShortcutChildren => true;
+
+        [JsonIgnore]
+        public override bool IsPreSorted => true;
+
+        private void ClearCache()
+        {
+            lock (_childIdsLock)
+            {
+                _childrenIds = null;
+            }
+        }
 
 
         protected override List<BaseItem> LoadChildren()
         protected override List<BaseItem> LoadChildren()
         {
         {
@@ -39,20 +59,6 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
-        [JsonIgnore]
-        public override bool SupportsInheritedParentImages => false;
-
-        [JsonIgnore]
-        public override bool SupportsPlayedStatus => false;
-
-        private void ClearCache()
-        {
-            lock (_childIdsLock)
-            {
-                _childrenIds = null;
-            }
-        }
-
         protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
         protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
         {
         {
             if (query.Recursive)
             if (query.Recursive)
@@ -74,12 +80,6 @@ namespace MediaBrowser.Controller.Entities
             return GetChildren(user, true).Count;
             return GetChildren(user, true).Count;
         }
         }
 
 
-        [JsonIgnore]
-        protected override bool SupportsShortcutChildren => true;
-
-        [JsonIgnore]
-        public override bool IsPreSorted => true;
-
         protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
         protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
         {
         {
             var list = base.GetEligibleChildrenForRecursiveChildren(user).ToList();
             var list = base.GetEligibleChildrenForRecursiveChildren(user).ToList();

+ 119 - 119
MediaBrowser.Controller/Entities/Video.cs

@@ -28,6 +28,14 @@ namespace MediaBrowser.Controller.Entities
         ISupportsPlaceHolders,
         ISupportsPlaceHolders,
         IHasMediaSources
         IHasMediaSources
     {
     {
+        public Video()
+        {
+            AdditionalParts = Array.Empty<string>();
+            LocalAlternateVersions = Array.Empty<string>();
+            SubtitleFiles = Array.Empty<string>();
+            LinkedAlternateVersions = Array.Empty<LinkedChild>();
+        }
+
         [JsonIgnore]
         [JsonIgnore]
         public string PrimaryVersionId { get; set; }
         public string PrimaryVersionId { get; set; }
 
 
@@ -74,30 +82,6 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
-        public void SetPrimaryVersionId(string id)
-        {
-            if (string.IsNullOrEmpty(id))
-            {
-                PrimaryVersionId = null;
-            }
-            else
-            {
-                PrimaryVersionId = id;
-            }
-
-            PresentationUniqueKey = CreatePresentationUniqueKey();
-        }
-
-        public override string CreatePresentationUniqueKey()
-        {
-            if (!string.IsNullOrEmpty(PrimaryVersionId))
-            {
-                return PrimaryVersionId;
-            }
-
-            return base.CreatePresentationUniqueKey();
-        }
-
         [JsonIgnore]
         [JsonIgnore]
         public override bool SupportsThemeMedia => true;
         public override bool SupportsThemeMedia => true;
 
 
@@ -151,24 +135,6 @@ namespace MediaBrowser.Controller.Entities
         /// <value>The aspect ratio.</value>
         /// <value>The aspect ratio.</value>
         public string AspectRatio { get; set; }
         public string AspectRatio { get; set; }
 
 
-        public Video()
-        {
-            AdditionalParts = Array.Empty<string>();
-            LocalAlternateVersions = Array.Empty<string>();
-            SubtitleFiles = Array.Empty<string>();
-            LinkedAlternateVersions = Array.Empty<LinkedChild>();
-        }
-
-        public override bool CanDownload()
-        {
-            if (VideoType == VideoType.Dvd || VideoType == VideoType.BluRay)
-            {
-                return false;
-            }
-
-            return IsFileProtocol;
-        }
-
         [JsonIgnore]
         [JsonIgnore]
         public override bool SupportsAddingToPlaylist => true;
         public override bool SupportsAddingToPlaylist => true;
 
 
@@ -196,16 +162,6 @@ namespace MediaBrowser.Controller.Entities
         [JsonIgnore]
         [JsonIgnore]
         public override bool HasLocalAlternateVersions => LocalAlternateVersions.Length > 0;
         public override bool HasLocalAlternateVersions => LocalAlternateVersions.Length > 0;
 
 
-        public IEnumerable<Guid> GetAdditionalPartIds()
-        {
-            return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
-        }
-
-        public IEnumerable<Guid> GetLocalAlternateVersionIds()
-        {
-            return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
-        }
-
         public static ILiveTvManager LiveTvManager { get; set; }
         public static ILiveTvManager LiveTvManager { get; set; }
 
 
         [JsonIgnore]
         [JsonIgnore]
@@ -222,37 +178,77 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
-        protected override bool IsActiveRecording()
+        [JsonIgnore]
+        public bool IsCompleteMedia
         {
         {
-            return LiveTvManager.GetActiveRecordingInfo(Path) != null;
+            get
+            {
+                if (SourceType == SourceType.Channel)
+                {
+                    return !Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase);
+                }
+
+                return !IsActiveRecording();
+            }
         }
         }
 
 
-        public override bool CanDelete()
+        [JsonIgnore]
+        protected virtual bool EnableDefaultVideoUserDataKeys => true;
+
+        [JsonIgnore]
+        public override string ContainingFolderPath
         {
         {
-            if (IsActiveRecording())
+            get
             {
             {
-                return false;
-            }
+                if (IsStacked)
+                {
+                    return System.IO.Path.GetDirectoryName(Path);
+                }
 
 
-            return base.CanDelete();
+                if (!IsPlaceHolder)
+                {
+                    if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
+                    {
+                        return Path;
+                    }
+                }
+
+                return base.ContainingFolderPath;
+            }
         }
         }
 
 
         [JsonIgnore]
         [JsonIgnore]
-        public bool IsCompleteMedia
+        public override string FileNameWithoutExtension
         {
         {
             get
             get
             {
             {
-                if (SourceType == SourceType.Channel)
+                if (IsFileProtocol)
                 {
                 {
-                    return !Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase);
+                    if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
+                    {
+                        return System.IO.Path.GetFileName(Path);
+                    }
+
+                    return System.IO.Path.GetFileNameWithoutExtension(Path);
                 }
                 }
 
 
-                return !IsActiveRecording();
+                return null;
             }
             }
         }
         }
 
 
+        /// <summary>
+        /// Gets a value indicating whether [is3 D].
+        /// </summary>
+        /// <value><c>true</c> if [is3 D]; otherwise, <c>false</c>.</value>
         [JsonIgnore]
         [JsonIgnore]
-        protected virtual bool EnableDefaultVideoUserDataKeys => true;
+        public bool Is3D => Video3DFormat.HasValue;
+
+        /// <summary>
+        /// Gets the type of the media.
+        /// </summary>
+        /// <value>The type of the media.</value>
+        [JsonIgnore]
+        public override string MediaType => Model.Entities.MediaType.Video;
 
 
         public override List<string> GetUserDataKeys()
         public override List<string> GetUserDataKeys()
         {
         {
@@ -293,6 +289,65 @@ namespace MediaBrowser.Controller.Entities
             return list;
             return list;
         }
         }
 
 
+        public void SetPrimaryVersionId(string id)
+        {
+            if (string.IsNullOrEmpty(id))
+            {
+                PrimaryVersionId = null;
+            }
+            else
+            {
+                PrimaryVersionId = id;
+            }
+
+            PresentationUniqueKey = CreatePresentationUniqueKey();
+        }
+
+        public override string CreatePresentationUniqueKey()
+        {
+            if (!string.IsNullOrEmpty(PrimaryVersionId))
+            {
+                return PrimaryVersionId;
+            }
+
+            return base.CreatePresentationUniqueKey();
+        }
+
+        public override bool CanDownload()
+        {
+            if (VideoType == VideoType.Dvd || VideoType == VideoType.BluRay)
+            {
+                return false;
+            }
+
+            return IsFileProtocol;
+        }
+
+        protected override bool IsActiveRecording()
+        {
+            return LiveTvManager.GetActiveRecordingInfo(Path) != null;
+        }
+
+        public override bool CanDelete()
+        {
+            if (IsActiveRecording())
+            {
+                return false;
+            }
+
+            return base.CanDelete();
+        }
+
+        public IEnumerable<Guid> GetAdditionalPartIds()
+        {
+            return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
+        }
+
+        public IEnumerable<Guid> GetLocalAlternateVersionIds()
+        {
+            return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
+        }
+
         private string GetUserDataKey(string providerId)
         private string GetUserDataKey(string providerId)
         {
         {
             var key = providerId + "-" + ExtraType.ToString().ToLowerInvariant();
             var key = providerId + "-" + ExtraType.ToString().ToLowerInvariant();
@@ -328,47 +383,6 @@ namespace MediaBrowser.Controller.Entities
                 .OrderBy(i => i.SortName);
                 .OrderBy(i => i.SortName);
         }
         }
 
 
-        [JsonIgnore]
-        public override string ContainingFolderPath
-        {
-            get
-            {
-                if (IsStacked)
-                {
-                    return System.IO.Path.GetDirectoryName(Path);
-                }
-
-                if (!IsPlaceHolder)
-                {
-                    if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
-                    {
-                        return Path;
-                    }
-                }
-
-                return base.ContainingFolderPath;
-            }
-        }
-
-        [JsonIgnore]
-        public override string FileNameWithoutExtension
-        {
-            get
-            {
-                if (IsFileProtocol)
-                {
-                    if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
-                    {
-                        return System.IO.Path.GetFileName(Path);
-                    }
-
-                    return System.IO.Path.GetFileNameWithoutExtension(Path);
-                }
-
-                return null;
-            }
-        }
-
         internal override ItemUpdateType UpdateFromResolvedItem(BaseItem newItem)
         internal override ItemUpdateType UpdateFromResolvedItem(BaseItem newItem)
         {
         {
             var updateType = base.UpdateFromResolvedItem(newItem);
             var updateType = base.UpdateFromResolvedItem(newItem);
@@ -397,20 +411,6 @@ namespace MediaBrowser.Controller.Entities
             return updateType;
             return updateType;
         }
         }
 
 
-        /// <summary>
-        /// Gets a value indicating whether [is3 D].
-        /// </summary>
-        /// <value><c>true</c> if [is3 D]; otherwise, <c>false</c>.</value>
-        [JsonIgnore]
-        public bool Is3D => Video3DFormat.HasValue;
-
-        /// <summary>
-        /// Gets the type of the media.
-        /// </summary>
-        /// <value>The type of the media.</value>
-        [JsonIgnore]
-        public override string MediaType => Model.Entities.MediaType.Video;
-
         protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
         protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
         {
         {
             var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
             var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);

+ 17 - 17
MediaBrowser.Controller/Entities/Year.cs

@@ -15,13 +15,11 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     /// </summary>
     public class Year : BaseItem, IItemByName
     public class Year : BaseItem, IItemByName
     {
     {
-        public override List<string> GetUserDataKeys()
-        {
-            var list = base.GetUserDataKeys();
+        [JsonIgnore]
+        public override bool SupportsAncestors => false;
 
 
-            list.Insert(0, "Year-" + Name);
-            return list;
-        }
+        [JsonIgnore]
+        public override bool SupportsPeople => false;
 
 
         /// <summary>
         /// <summary>
         /// Gets the folder containing the item.
         /// Gets the folder containing the item.
@@ -31,6 +29,19 @@ namespace MediaBrowser.Controller.Entities
         [JsonIgnore]
         [JsonIgnore]
         public override string ContainingFolderPath => Path;
         public override string ContainingFolderPath => Path;
 
 
+        public override bool CanDelete()
+        {
+            return false;
+        }
+
+        public override List<string> GetUserDataKeys()
+        {
+            var list = base.GetUserDataKeys();
+
+            list.Insert(0, "Year-" + Name);
+            return list;
+        }
+
         public override double GetDefaultPrimaryImageAspectRatio()
         public override double GetDefaultPrimaryImageAspectRatio()
         {
         {
             double value = 2;
             double value = 2;
@@ -39,14 +50,6 @@ namespace MediaBrowser.Controller.Entities
             return value;
             return value;
         }
         }
 
 
-        [JsonIgnore]
-        public override bool SupportsAncestors => false;
-
-        public override bool CanDelete()
-        {
-            return false;
-        }
-
         public override bool IsSaveLocalMetadataEnabled()
         public override bool IsSaveLocalMetadataEnabled()
         {
         {
             return true;
             return true;
@@ -76,9 +79,6 @@ namespace MediaBrowser.Controller.Entities
             return null;
             return null;
         }
         }
 
 
-        [JsonIgnore]
-        public override bool SupportsPeople => false;
-
         public static string GetPath(string name)
         public static string GetPath(string name)
         {
         {
             return GetPath(name, true);
             return GetPath(name, true);

+ 0 - 3
MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs

@@ -7,7 +7,6 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
-using System.Runtime.InteropServices;
 using System.Text;
 using System.Text;
 using System.Text.RegularExpressions;
 using System.Text.RegularExpressions;
 using System.Threading;
 using System.Threading;
@@ -16,9 +15,7 @@ using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.MediaInfo;
-using Microsoft.Extensions.Configuration;
 
 
 namespace MediaBrowser.Controller.MediaEncoding
 namespace MediaBrowser.Controller.MediaEncoding
 {
 {

+ 0 - 6
MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs

@@ -10,7 +10,6 @@ using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.System;
 
 
 namespace MediaBrowser.Controller.MediaEncoding
 namespace MediaBrowser.Controller.MediaEncoding
 {
 {
@@ -19,11 +18,6 @@ namespace MediaBrowser.Controller.MediaEncoding
     /// </summary>
     /// </summary>
     public interface IMediaEncoder : ITranscoderSupport
     public interface IMediaEncoder : ITranscoderSupport
     {
     {
-        /// <summary>
-        /// Gets location of the discovered FFmpeg tool.
-        /// </summary>
-        FFmpegLocation EncoderLocation { get; }
-
         /// <summary>
         /// <summary>
         /// Gets the encoder path.
         /// Gets the encoder path.
         /// </summary>
         /// </summary>

+ 16 - 16
MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs

@@ -1,6 +1,6 @@
 #nullable disable
 #nullable disable
 
 
-#pragma warning disable CS1591
+#pragma warning disable CS1591, SA1306, SA1401
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -30,6 +30,21 @@ namespace MediaBrowser.Controller.Net
         private readonly List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>> _activeConnections =
         private readonly List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>> _activeConnections =
             new List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>>();
             new List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>>();
 
 
+        /// <summary>
+        /// The logger.
+        /// </summary>
+        protected ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> Logger;
+
+        protected BasePeriodicWebSocketListener(ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> logger)
+        {
+            if (logger == null)
+            {
+                throw new ArgumentNullException(nameof(logger));
+            }
+
+            Logger = logger;
+        }
+
         /// <summary>
         /// <summary>
         /// Gets the type used for the messages sent to the client.
         /// Gets the type used for the messages sent to the client.
         /// </summary>
         /// </summary>
@@ -54,21 +69,6 @@ namespace MediaBrowser.Controller.Net
         /// <returns>Task{`1}.</returns>
         /// <returns>Task{`1}.</returns>
         protected abstract Task<TReturnDataType> GetDataToSend();
         protected abstract Task<TReturnDataType> GetDataToSend();
 
 
-        /// <summary>
-        /// The logger.
-        /// </summary>
-        protected ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> Logger;
-
-        protected BasePeriodicWebSocketListener(ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> logger)
-        {
-            if (logger == null)
-            {
-                throw new ArgumentNullException(nameof(logger));
-            }
-
-            Logger = logger;
-        }
-
         /// <summary>
         /// <summary>
         /// Processes the message.
         /// Processes the message.
         /// </summary>
         /// </summary>

+ 0 - 1
MediaBrowser.Controller/Persistence/IUserDataRepository.cs

@@ -18,7 +18,6 @@ namespace MediaBrowser.Controller.Persistence
         /// <param name="key">The key.</param>
         /// <param name="key">The key.</param>
         /// <param name="userData">The user data.</param>
         /// <param name="userData">The user data.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
         void SaveUserData(long userId, string key, UserItemData userData, CancellationToken cancellationToken);
         void SaveUserData(long userId, string key, UserItemData userData, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>

+ 39 - 39
MediaBrowser.Controller/Playlists/Playlist.cs

@@ -31,24 +31,18 @@ namespace MediaBrowser.Controller.Playlists
             ".zpl"
             ".zpl"
         };
         };
 
 
-        public Guid OwnerUserId { get; set; }
-
-        public Share[] Shares { get; set; }
-
         public Playlist()
         public Playlist()
         {
         {
             Shares = Array.Empty<Share>();
             Shares = Array.Empty<Share>();
         }
         }
 
 
+        public Guid OwnerUserId { get; set; }
+
+        public Share[] Shares { get; set; }
+
         [JsonIgnore]
         [JsonIgnore]
         public bool IsFile => IsPlaylistFile(Path);
         public bool IsFile => IsPlaylistFile(Path);
 
 
-        public static bool IsPlaylistFile(string path)
-        {
-            // The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot).
-            return System.IO.Path.HasExtension(path) && !Directory.Exists(path);
-        }
-
         [JsonIgnore]
         [JsonIgnore]
         public override string ContainingFolderPath
         public override string ContainingFolderPath
         {
         {
@@ -80,6 +74,41 @@ namespace MediaBrowser.Controller.Playlists
         [JsonIgnore]
         [JsonIgnore]
         public override bool SupportsCumulativeRunTimeTicks => true;
         public override bool SupportsCumulativeRunTimeTicks => true;
 
 
+        [JsonIgnore]
+        public override bool IsPreSorted => true;
+
+        public string PlaylistMediaType { get; set; }
+
+        [JsonIgnore]
+        public override string MediaType => PlaylistMediaType;
+
+        [JsonIgnore]
+        private bool IsSharedItem
+        {
+            get
+            {
+                var path = Path;
+
+                if (string.IsNullOrEmpty(path))
+                {
+                    return false;
+                }
+
+                return FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, path);
+            }
+        }
+
+        public static bool IsPlaylistFile(string path)
+        {
+            // The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot).
+            return System.IO.Path.HasExtension(path) && !Directory.Exists(path);
+        }
+
+        public void SetMediaType(string value)
+        {
+            PlaylistMediaType = value;
+        }
+
         public override double GetDefaultPrimaryImageAspectRatio()
         public override double GetDefaultPrimaryImageAspectRatio()
         {
         {
             return 1;
             return 1;
@@ -197,35 +226,6 @@ namespace MediaBrowser.Controller.Playlists
             return new[] { item };
             return new[] { item };
         }
         }
 
 
-        [JsonIgnore]
-        public override bool IsPreSorted => true;
-
-        public string PlaylistMediaType { get; set; }
-
-        [JsonIgnore]
-        public override string MediaType => PlaylistMediaType;
-
-        public void SetMediaType(string value)
-        {
-            PlaylistMediaType = value;
-        }
-
-        [JsonIgnore]
-        private bool IsSharedItem
-        {
-            get
-            {
-                var path = Path;
-
-                if (string.IsNullOrEmpty(path))
-                {
-                    return false;
-                }
-
-                return FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, path);
-            }
-        }
-
         public override bool IsVisible(User user)
         public override bool IsVisible(User user)
         {
         {
             if (!IsSharedItem)
             if (!IsSharedItem)

+ 1 - 1
MediaBrowser.Controller/Providers/IDirectoryService.cs

@@ -1,4 +1,4 @@
-#pragma warning disable CA1002, CS1591
+#pragma warning disable CA1002, CA1819, CS1591
 
 
 using System.Collections.Generic;
 using System.Collections.Generic;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;

+ 10 - 10
MediaBrowser.Controller/Resolvers/IItemResolver.cs

@@ -13,18 +13,18 @@ namespace MediaBrowser.Controller.Resolvers
     /// </summary>
     /// </summary>
     public interface IItemResolver
     public interface IItemResolver
     {
     {
+        /// <summary>
+        /// Gets the priority.
+        /// </summary>
+        /// <value>The priority.</value>
+        ResolverPriority Priority { get; }
+
         /// <summary>
         /// <summary>
         /// Resolves the path.
         /// Resolves the path.
         /// </summary>
         /// </summary>
         /// <param name="args">The args.</param>
         /// <param name="args">The args.</param>
         /// <returns>BaseItem.</returns>
         /// <returns>BaseItem.</returns>
         BaseItem ResolvePath(ItemResolveArgs args);
         BaseItem ResolvePath(ItemResolveArgs args);
-
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        ResolverPriority Priority { get; }
     }
     }
 
 
     public interface IMultiItemResolver
     public interface IMultiItemResolver
@@ -38,14 +38,14 @@ namespace MediaBrowser.Controller.Resolvers
 
 
     public class MultiItemResolverResult
     public class MultiItemResolverResult
     {
     {
-        public List<BaseItem> Items { get; set; }
-
-        public List<FileSystemMetadata> ExtraFiles { get; set; }
-
         public MultiItemResolverResult()
         public MultiItemResolverResult()
         {
         {
             Items = new List<BaseItem>();
             Items = new List<BaseItem>();
             ExtraFiles = new List<FileSystemMetadata>();
             ExtraFiles = new List<FileSystemMetadata>();
         }
         }
+
+        public List<BaseItem> Items { get; set; }
+
+        public List<FileSystemMetadata> ExtraFiles { get; set; }
     }
     }
 }
 }

+ 19 - 0
MediaBrowser.Controller/Subtitles/ISubtitleManager.cs

@@ -28,6 +28,11 @@ namespace MediaBrowser.Controller.Subtitles
         /// <summary>
         /// <summary>
         /// Searches the subtitles.
         /// Searches the subtitles.
         /// </summary>
         /// </summary>
+        /// <param name="video">The video.</param>
+        /// <param name="language">Subtitle language.</param>
+        /// <param name="isPerfectMatch">Require perfect match.</param>
+        /// <param name="cancellationToken">CancellationToken to use for the operation.</param>
+        /// <returns>Subtitles, wrapped in task.</returns>
         Task<RemoteSubtitleInfo[]> SearchSubtitles(
         Task<RemoteSubtitleInfo[]> SearchSubtitles(
             Video video,
             Video video,
             string language,
             string language,
@@ -47,11 +52,20 @@ namespace MediaBrowser.Controller.Subtitles
         /// <summary>
         /// <summary>
         /// Downloads the subtitles.
         /// Downloads the subtitles.
         /// </summary>
         /// </summary>
+        /// <param name="video">The video.</param>
+        /// <param name="subtitleId">Subtitle ID.</param>
+        /// <param name="cancellationToken">CancellationToken to use for the operation.</param>
+        /// <returns>A task.</returns>
         Task DownloadSubtitles(Video video, string subtitleId, CancellationToken cancellationToken);
         Task DownloadSubtitles(Video video, string subtitleId, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Downloads the subtitles.
         /// Downloads the subtitles.
         /// </summary>
         /// </summary>
+        /// <param name="video">The video.</param>
+        /// <param name="libraryOptions">Library options to use.</param>
+        /// <param name="subtitleId">Subtitle ID.</param>
+        /// <param name="cancellationToken">CancellationToken to use for the operation.</param>
+        /// <returns>A task.</returns>
         Task DownloadSubtitles(Video video, LibraryOptions libraryOptions, string subtitleId, CancellationToken cancellationToken);
         Task DownloadSubtitles(Video video, LibraryOptions libraryOptions, string subtitleId, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
@@ -73,11 +87,16 @@ namespace MediaBrowser.Controller.Subtitles
         /// <summary>
         /// <summary>
         /// Deletes the subtitles.
         /// Deletes the subtitles.
         /// </summary>
         /// </summary>
+        /// <param name="item">Media item.</param>
+        /// <param name="index">Subtitle index.</param>
+        /// <returns>A task.</returns>
         Task DeleteSubtitles(BaseItem item, int index);
         Task DeleteSubtitles(BaseItem item, int index);
 
 
         /// <summary>
         /// <summary>
         /// Gets the providers.
         /// Gets the providers.
         /// </summary>
         /// </summary>
+        /// <param name="item">The media item.</param>
+        /// <returns>Subtitles providers.</returns>
         SubtitleProviderInfo[] GetSupportedProviders(BaseItem item);
         SubtitleProviderInfo[] GetSupportedProviders(BaseItem item);
     }
     }
 }
 }

+ 9 - 9
MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs

@@ -11,6 +11,15 @@ namespace MediaBrowser.Controller.Subtitles
 {
 {
     public class SubtitleSearchRequest : IHasProviderIds
     public class SubtitleSearchRequest : IHasProviderIds
     {
     {
+        public SubtitleSearchRequest()
+        {
+            SearchAllProviders = true;
+            ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+
+            DisabledSubtitleFetchers = Array.Empty<string>();
+            SubtitleFetcherOrder = Array.Empty<string>();
+        }
+
         public string Language { get; set; }
         public string Language { get; set; }
 
 
         public string TwoLetterISOLanguageName { get; set; }
         public string TwoLetterISOLanguageName { get; set; }
@@ -42,14 +51,5 @@ namespace MediaBrowser.Controller.Subtitles
         public string[] DisabledSubtitleFetchers { get; set; }
         public string[] DisabledSubtitleFetchers { get; set; }
 
 
         public string[] SubtitleFetcherOrder { get; set; }
         public string[] SubtitleFetcherOrder { get; set; }
-
-        public SubtitleSearchRequest()
-        {
-            SearchAllProviders = true;
-            ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
-            DisabledSubtitleFetchers = Array.Empty<string>();
-            SubtitleFetcherOrder = Array.Empty<string>();
-        }
     }
     }
 }
 }

+ 1 - 3
MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs

@@ -12,8 +12,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
 {
 {
     public class EncoderValidator
     public class EncoderValidator
     {
     {
-        private const string DefaultEncoderPath = "ffmpeg";
-
         private static readonly string[] _requiredDecoders = new[]
         private static readonly string[] _requiredDecoders = new[]
         {
         {
             "h264",
             "h264",
@@ -124,7 +122,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
         private readonly string _encoderPath;
         private readonly string _encoderPath;
 
 
-        public EncoderValidator(ILogger logger, string encoderPath = DefaultEncoderPath)
+        public EncoderValidator(ILogger logger, string encoderPath)
         {
         {
             _logger = logger;
             _logger = logger;
             _encoderPath = encoderPath;
             _encoderPath = encoderPath;

+ 32 - 75
MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs

@@ -23,7 +23,6 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.System;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
 
 
@@ -72,7 +71,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
         private Version _ffmpegVersion = null;
         private Version _ffmpegVersion = null;
         private string _ffmpegPath = string.Empty;
         private string _ffmpegPath = string.Empty;
         private string _ffprobePath;
         private string _ffprobePath;
-        private int threads;
+        private int _threads;
 
 
         public MediaEncoder(
         public MediaEncoder(
             ILogger<MediaEncoder> logger,
             ILogger<MediaEncoder> logger,
@@ -92,9 +91,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// <inheritdoc />
         /// <inheritdoc />
         public string EncoderPath => _ffmpegPath;
         public string EncoderPath => _ffmpegPath;
 
 
-        /// <inheritdoc />
-        public FFmpegLocation EncoderLocation { get; private set; }
-
         /// <summary>
         /// <summary>
         /// Run at startup or if the user removes a Custom path from transcode page.
         /// Run at startup or if the user removes a Custom path from transcode page.
         /// Sets global variables FFmpegPath.
         /// Sets global variables FFmpegPath.
@@ -103,20 +99,23 @@ namespace MediaBrowser.MediaEncoding.Encoder
         public void SetFFmpegPath()
         public void SetFFmpegPath()
         {
         {
             // 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence
             // 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence
-            if (!ValidatePath(_configurationManager.GetEncodingOptions().EncoderAppPath, FFmpegLocation.Custom))
+            var ffmpegPath = _configurationManager.GetEncodingOptions().EncoderAppPath;
+            if (string.IsNullOrEmpty(ffmpegPath))
             {
             {
                 // 2) Check if the --ffmpeg CLI switch has been given
                 // 2) Check if the --ffmpeg CLI switch has been given
-                if (!ValidatePath(_startupOptionFFmpegPath, FFmpegLocation.SetByArgument))
+                ffmpegPath = _startupOptionFFmpegPath;
+                if (string.IsNullOrEmpty(ffmpegPath))
                 {
                 {
-                    // 3) Search system $PATH environment variable for valid FFmpeg
-                    if (!ValidatePath(ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System))
-                    {
-                        EncoderLocation = FFmpegLocation.NotFound;
-                        _ffmpegPath = null;
-                    }
+                    // 3) Check "ffmpeg"
+                    ffmpegPath = "ffmpeg";
                 }
                 }
             }
             }
 
 
+            if (!ValidatePath(ffmpegPath))
+            {
+                _ffmpegPath = null;
+            }
+
             // Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI
             // Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI
             var config = _configurationManager.GetEncodingOptions();
             var config = _configurationManager.GetEncodingOptions();
             config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty;
             config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty;
@@ -138,10 +137,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 SetAvailableHwaccels(validator.GetHwaccels());
                 SetAvailableHwaccels(validator.GetHwaccels());
                 SetMediaEncoderVersion(validator);
                 SetMediaEncoderVersion(validator);
 
 
-                threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null);
+                _threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null);
             }
             }
 
 
-            _logger.LogInformation("FFmpeg: {EncoderLocation}: {FfmpegPath}", EncoderLocation, _ffmpegPath ?? string.Empty);
+            _logger.LogInformation("FFmpeg: {FfmpegPath}", _ffmpegPath ?? string.Empty);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -160,15 +159,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
             {
             {
                 throw new ArgumentException("Unexpected pathType value");
                 throw new ArgumentException("Unexpected pathType value");
             }
             }
-            else if (string.IsNullOrWhiteSpace(path))
+
+            if (string.IsNullOrWhiteSpace(path))
             {
             {
                 // User had cleared the custom path in UI
                 // User had cleared the custom path in UI
                 newPath = string.Empty;
                 newPath = string.Empty;
             }
             }
-            else if (File.Exists(path))
-            {
-                newPath = path;
-            }
             else if (Directory.Exists(path))
             else if (Directory.Exists(path))
             {
             {
                 // Given path is directory, so resolve down to filename
                 // Given path is directory, so resolve down to filename
@@ -176,7 +172,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             }
             }
             else
             else
             {
             {
-                throw new ResourceNotFoundException();
+                newPath = path;
             }
             }
 
 
             // Write the new ffmpeg path to the xml as <EncoderAppPath>
             // Write the new ffmpeg path to the xml as <EncoderAppPath>
@@ -191,37 +187,26 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
         /// <summary>
         /// <summary>
         /// Validates the supplied FQPN to ensure it is a ffmpeg utility.
         /// Validates the supplied FQPN to ensure it is a ffmpeg utility.
-        /// If checks pass, global variable FFmpegPath and EncoderLocation are updated.
+        /// If checks pass, global variable FFmpegPath is updated.
         /// </summary>
         /// </summary>
         /// <param name="path">FQPN to test.</param>
         /// <param name="path">FQPN to test.</param>
-        /// <param name="location">Location (External, Custom, System) of tool.</param>
         /// <returns><c>true</c> if the version validation succeeded; otherwise, <c>false</c>.</returns>
         /// <returns><c>true</c> if the version validation succeeded; otherwise, <c>false</c>.</returns>
-        private bool ValidatePath(string path, FFmpegLocation location)
+        private bool ValidatePath(string path)
         {
         {
-            bool rc = false;
-
-            if (!string.IsNullOrEmpty(path))
+            if (string.IsNullOrEmpty(path))
             {
             {
-                if (File.Exists(path))
-                {
-                    rc = new EncoderValidator(_logger, path).ValidateVersion();
-
-                    if (!rc)
-                    {
-                        _logger.LogWarning("FFmpeg: {Location}: Failed version check: {Path}", location, path);
-                    }
+                return false;
+            }
 
 
-                    _ffmpegPath = path;
-                    EncoderLocation = location;
-                    return true;
-                }
-                else
-                {
-                    _logger.LogWarning("FFmpeg: {Location}: File not found: {Path}", location, path);
-                }
+            bool rc = new EncoderValidator(_logger, path).ValidateVersion();
+            if (!rc)
+            {
+                _logger.LogWarning("FFmpeg: Failed version check: {Path}", path);
+                return false;
             }
             }
 
 
-            return rc;
+            _ffmpegPath = path;
+            return true;
         }
         }
 
 
         private string GetEncoderPathFromDirectory(string path, string filename, bool recursive = false)
         private string GetEncoderPathFromDirectory(string path, string filename, bool recursive = false)
@@ -242,34 +227,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
             }
             }
         }
         }
 
 
-        /// <summary>
-        /// Search the system $PATH environment variable looking for given filename.
-        /// </summary>
-        /// <param name="fileName">The filename.</param>
-        /// <returns>The full path to the file.</returns>
-        private string ExistsOnSystemPath(string fileName)
-        {
-            var inJellyfinPath = GetEncoderPathFromDirectory(AppContext.BaseDirectory, fileName, recursive: true);
-            if (!string.IsNullOrEmpty(inJellyfinPath))
-            {
-                return inJellyfinPath;
-            }
-
-            var values = Environment.GetEnvironmentVariable("PATH");
-
-            foreach (var path in values.Split(Path.PathSeparator))
-            {
-                var candidatePath = GetEncoderPathFromDirectory(path, fileName);
-
-                if (!string.IsNullOrEmpty(candidatePath))
-                {
-                    return candidatePath;
-                }
-            }
-
-            return null;
-        }
-
         public void SetAvailableEncoders(IEnumerable<string> list)
         public void SetAvailableEncoders(IEnumerable<string> list)
         {
         {
             _encoders = list.ToList();
             _encoders = list.ToList();
@@ -425,7 +382,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             var args = extractChapters
             var args = extractChapters
                 ? "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_chapters -show_format"
                 ? "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_chapters -show_format"
                 : "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_format";
                 : "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_format";
-            args = string.Format(CultureInfo.InvariantCulture, args, probeSizeArgument, inputPath, threads).Trim();
+            args = string.Format(CultureInfo.InvariantCulture, args, probeSizeArgument, inputPath, _threads).Trim();
 
 
             var process = new Process
             var process = new Process
             {
             {
@@ -646,7 +603,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 }
                 }
             }
             }
 
 
-            var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, threads);
+            var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, _threads);
 
 
             if (offset.HasValue)
             if (offset.HasValue)
             {
             {
@@ -759,7 +716,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             Directory.CreateDirectory(targetDirectory);
             Directory.CreateDirectory(targetDirectory);
             var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg");
             var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg");
 
 
-            var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads {3} -v quiet {2} -f image2 \"{1}\"", inputArgument, outputPath, vf, threads);
+            var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads {3} -v quiet {2} -f image2 \"{1}\"", inputArgument, outputPath, vf, _threads);
 
 
             if (!string.IsNullOrWhiteSpace(container))
             if (!string.IsNullOrWhiteSpace(container))
             {
             {

+ 1 - 10
MediaBrowser.Model/Globalization/ILocalizationManager.cs

@@ -1,4 +1,3 @@
-#nullable disable
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
@@ -56,19 +55,11 @@ namespace MediaBrowser.Model.Globalization
         /// <returns><see cref="IEnumerable{LocalizatonOption}" />.</returns>
         /// <returns><see cref="IEnumerable{LocalizatonOption}" />.</returns>
         IEnumerable<LocalizationOption> GetLocalizationOptions();
         IEnumerable<LocalizationOption> GetLocalizationOptions();
 
 
-        /// <summary>
-        /// Checks if the string contains a character with the specified unicode category.
-        /// </summary>
-        /// <param name="value">The string.</param>
-        /// <param name="category">The unicode category.</param>
-        /// <returns>Wether or not the string contains a character with the specified unicode category.</returns>
-        bool HasUnicodeCategory(string value, UnicodeCategory category);
-
         /// <summary>
         /// <summary>
         /// Returns the correct <see cref="CultureInfo" /> for the given language.
         /// Returns the correct <see cref="CultureInfo" /> for the given language.
         /// </summary>
         /// </summary>
         /// <param name="language">The language.</param>
         /// <param name="language">The language.</param>
         /// <returns>The correct <see cref="CultureInfo" /> for the given language.</returns>
         /// <returns>The correct <see cref="CultureInfo" /> for the given language.</returns>
-        CultureDto FindLanguageInfo(string language);
+        CultureDto? FindLanguageInfo(string language);
     }
     }
 }
 }

+ 1 - 0
MediaBrowser.Model/System/SystemInfo.cs

@@ -133,6 +133,7 @@ namespace MediaBrowser.Model.System
         [Obsolete("This should be handled by the package manager")]
         [Obsolete("This should be handled by the package manager")]
         public bool HasUpdateAvailable { get; set; }
         public bool HasUpdateAvailable { get; set; }
 
 
+        [Obsolete("This isn't set correctly anymore")]
         public FFmpegLocation EncoderLocation { get; set; }
         public FFmpegLocation EncoderLocation { get; set; }
 
 
         public Architecture SystemArchitecture { get; set; }
         public Architecture SystemArchitecture { get; set; }

+ 4 - 4
MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs

@@ -59,9 +59,9 @@ namespace MediaBrowser.Providers.BoxSets
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        protected override ItemUpdateType BeforeSaveInternal(BoxSet item, bool isFullRefresh, ItemUpdateType currentUpdateType)
+        protected override ItemUpdateType BeforeSaveInternal(BoxSet item, bool isFullRefresh, ItemUpdateType updateType)
         {
         {
-            var updateType = base.BeforeSaveInternal(item, isFullRefresh, currentUpdateType);
+            var updatedType = base.BeforeSaveInternal(item, isFullRefresh, updateType);
 
 
             var libraryFolderIds = item.GetLibraryFolderIds();
             var libraryFolderIds = item.GetLibraryFolderIds();
 
 
@@ -69,10 +69,10 @@ namespace MediaBrowser.Providers.BoxSets
             if (itemLibraryFolderIds == null || !libraryFolderIds.SequenceEqual(itemLibraryFolderIds))
             if (itemLibraryFolderIds == null || !libraryFolderIds.SequenceEqual(itemLibraryFolderIds))
             {
             {
                 item.LibraryFolderIds = libraryFolderIds;
                 item.LibraryFolderIds = libraryFolderIds;
-                updateType |= ItemUpdateType.MetadataImport;
+                updatedType |= ItemUpdateType.MetadataImport;
             }
             }
 
 
-            return updateType;
+            return updatedType;
         }
         }
     }
     }
 }
 }

+ 3 - 1
MediaBrowser.Providers/Manager/ItemImageProvider.cs

@@ -1,7 +1,8 @@
-#pragma warning disable CS1591
+#pragma warning disable CA1002, CS1591
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Net;
 using System.Net;
@@ -536,6 +537,7 @@ namespace MediaBrowser.Providers.Manager
                     return true;
                     return true;
                 }
                 }
             }
             }
+
             // We always want to use prefetched images
             // We always want to use prefetched images
             return false;
             return false;
         }
         }

+ 5 - 0
MediaBrowser.Providers/Manager/MetadataService.cs

@@ -505,6 +505,11 @@ namespace MediaBrowser.Providers.Manager
         /// <summary>
         /// <summary>
         /// Gets the providers.
         /// Gets the providers.
         /// </summary>
         /// </summary>
+        /// <param name="item">A media item.</param>
+        /// <param name="libraryOptions">The LibraryOptions to use.</param>
+        /// <param name="options">The MetadataRefreshOptions to use.</param>
+        /// <param name="isFirstRefresh">Specifies first refresh mode.</param>
+        /// <param name="requiresRefresh">Specifies refresh mode.</param>
         /// <returns>IEnumerable{`0}.</returns>
         /// <returns>IEnumerable{`0}.</returns>
         protected IEnumerable<IMetadataProvider> GetProviders(BaseItem item, LibraryOptions libraryOptions, MetadataRefreshOptions options, bool isFirstRefresh, bool requiresRefresh)
         protected IEnumerable<IMetadataProvider> GetProviders(BaseItem item, LibraryOptions libraryOptions, MetadataRefreshOptions options, bool isFirstRefresh, bool requiresRefresh)
         {
         {

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно