aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations')
-rw-r--r--Emby.Server.Implementations/Cryptography/CryptographyProvider.cs27
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj3
-rw-r--r--Emby.Server.Implementations/IO/LibraryMonitor.cs6
-rw-r--r--Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs6
-rw-r--r--Emby.Server.Implementations/Library/DotIgnoreIgnoreRule.cs48
-rw-r--r--Emby.Server.Implementations/Library/IgnorePatterns.cs1
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs40
-rw-r--r--Emby.Server.Implementations/Localization/Core/ar.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/be.json18
-rw-r--r--Emby.Server.Implementations/Localization/Core/cy.json15
-rw-r--r--Emby.Server.Implementations/Localization/Core/de.json14
-rw-r--r--Emby.Server.Implementations/Localization/Core/et.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/ml.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/my.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/th.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/tr.json22
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-CN.json54
-rw-r--r--Emby.Server.Implementations/Localization/LocalizationManager.cs45
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs19
-rw-r--r--Emby.Server.Implementations/Sorting/StudioComparer.cs4
-rw-r--r--Emby.Server.Implementations/Updates/InstallationManager.cs7
21 files changed, 245 insertions, 106 deletions
diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
index 5380c45d8..0381c4d35 100644
--- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
+++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
@@ -39,22 +39,24 @@ namespace Emby.Server.Implementations.Cryptography
{
if (string.Equals(hash.Id, "PBKDF2", StringComparison.Ordinal))
{
+ var iterations = GetIterationsParameter(hash);
return hash.Hash.SequenceEqual(
Rfc2898DeriveBytes.Pbkdf2(
password,
hash.Salt,
- int.Parse(hash.Parameters["iterations"], CultureInfo.InvariantCulture),
+ iterations,
HashAlgorithmName.SHA1,
32));
}
if (string.Equals(hash.Id, "PBKDF2-SHA512", StringComparison.Ordinal))
{
+ var iterations = GetIterationsParameter(hash);
return hash.Hash.SequenceEqual(
Rfc2898DeriveBytes.Pbkdf2(
password,
hash.Salt,
- int.Parse(hash.Parameters["iterations"], CultureInfo.InvariantCulture),
+ iterations,
HashAlgorithmName.SHA512,
DefaultOutputLength));
}
@@ -62,6 +64,27 @@ namespace Emby.Server.Implementations.Cryptography
throw new NotSupportedException($"Can't verify hash with id: {hash.Id}");
}
+ /// <summary>
+ /// Extracts and validates the iterations parameter from a password hash.
+ /// </summary>
+ /// <param name="hash">The password hash containing parameters.</param>
+ /// <returns>The number of iterations.</returns>
+ /// <exception cref="FormatException">Thrown when iterations parameter is missing or invalid.</exception>
+ private static int GetIterationsParameter(PasswordHash hash)
+ {
+ if (!hash.Parameters.TryGetValue("iterations", out var iterationsStr))
+ {
+ throw new FormatException($"Password hash with id '{hash.Id}' is missing required 'iterations' parameter.");
+ }
+
+ if (!int.TryParse(iterationsStr, CultureInfo.InvariantCulture, out var iterations))
+ {
+ throw new FormatException($"Password hash with id '{hash.Id}' has invalid 'iterations' parameter: '{iterationsStr}'.");
+ }
+
+ return iterations;
+ }
+
/// <inheritdoc />
public byte[] GenerateSalt()
=> GenerateSalt(DefaultSaltLength);
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 15843730e..f312fb4db 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -27,7 +27,6 @@
<PackageReference Include="Microsoft.Data.Sqlite" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" />
<PackageReference Include="prometheus-net.DotNetRuntime" />
@@ -39,7 +38,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>net9.0</TargetFramework>
+ <TargetFramework>net10.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs
index d87ad729e..7cff2a25b 100644
--- a/Emby.Server.Implementations/IO/LibraryMonitor.cs
+++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs
@@ -352,6 +352,12 @@ namespace Emby.Server.Implementations.IO
return;
}
+ var fileInfo = _fileSystem.GetFileSystemInfo(path);
+ if (DotIgnoreIgnoreRule.IsIgnored(fileInfo, null))
+ {
+ return;
+ }
+
// Ignore certain files, If the parent of an ignored path has a change event, ignore that too
foreach (var i in _tempIgnoredPaths.Keys)
{
diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
index 273d356a3..a25373326 100644
--- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
+++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
@@ -98,5 +98,11 @@ namespace Emby.Server.Implementations.Images
return base.CreateImage(item, itemsWithImages, outputPath, imageType, imageIndex);
}
+
+ protected override bool HasChangedByDate(BaseItem item, ItemImageInfo image)
+ {
+ var age = DateTime.UtcNow - image.DateModified;
+ return age.TotalDays > 7;
+ }
}
}
diff --git a/Emby.Server.Implementations/Library/DotIgnoreIgnoreRule.cs b/Emby.Server.Implementations/Library/DotIgnoreIgnoreRule.cs
index 473ff8e1d..ef5d24c70 100644
--- a/Emby.Server.Implementations/Library/DotIgnoreIgnoreRule.cs
+++ b/Emby.Server.Implementations/Library/DotIgnoreIgnoreRule.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Text.RegularExpressions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Resolvers;
@@ -70,12 +71,55 @@ public class DotIgnoreIgnoreRule : IResolverIgnoreRule
{
// If file has content, base ignoring off the content .gitignore-style rules
var rules = ignoreFileContent.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+ return CheckIgnoreRules(path, rules, isDirectory);
+ }
+
+ /// <summary>
+ /// Checks whether a path should be ignored based on an array of ignore rules.
+ /// </summary>
+ /// <param name="path">The path to check.</param>
+ /// <param name="rules">The array of ignore rules.</param>
+ /// <param name="isDirectory">Whether the path is a directory.</param>
+ /// <returns>True if the path should be ignored.</returns>
+ internal static bool CheckIgnoreRules(string path, string[] rules, bool isDirectory)
+ => CheckIgnoreRules(path, rules, isDirectory, IsWindows);
+
+ /// <summary>
+ /// Checks whether a path should be ignored based on an array of ignore rules.
+ /// </summary>
+ /// <param name="path">The path to check.</param>
+ /// <param name="rules">The array of ignore rules.</param>
+ /// <param name="isDirectory">Whether the path is a directory.</param>
+ /// <param name="normalizePath">Whether to normalize backslashes to forward slashes (for Windows paths).</param>
+ /// <returns>True if the path should be ignored.</returns>
+ internal static bool CheckIgnoreRules(string path, string[] rules, bool isDirectory, bool normalizePath)
+ {
var ignore = new Ignore.Ignore();
- ignore.Add(rules);
+
+ // Add each rule individually to catch and skip invalid patterns
+ var validRulesAdded = 0;
+ foreach (var rule in rules)
+ {
+ try
+ {
+ ignore.Add(rule);
+ validRulesAdded++;
+ }
+ catch (RegexParseException)
+ {
+ // Ignore invalid patterns
+ }
+ }
+
+ // If no valid rules were added, fall back to ignoring everything (like an empty .ignore file)
+ if (validRulesAdded == 0)
+ {
+ return true;
+ }
// Mitigate the problem of the Ignore library not handling Windows paths correctly.
// See https://github.com/jellyfin/jellyfin/issues/15484
- var pathToCheck = IsWindows ? path.NormalizePath('/') : path;
+ var pathToCheck = normalizePath ? path.NormalizePath('/') : path;
// Add trailing slash for directories to match "folder/"
if (isDirectory)
diff --git a/Emby.Server.Implementations/Library/IgnorePatterns.cs b/Emby.Server.Implementations/Library/IgnorePatterns.cs
index fe3a1ce61..5fac2f6b0 100644
--- a/Emby.Server.Implementations/Library/IgnorePatterns.cs
+++ b/Emby.Server.Implementations/Library/IgnorePatterns.cs
@@ -83,6 +83,7 @@ namespace Emby.Server.Implementations.Library
// Unix hidden files
"**/.*",
+ "**/.*/**",
// Mac - if you ever remove the above.
// "**/._*",
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 30c3e89b4..f7f5c387e 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -1058,6 +1058,7 @@ namespace Emby.Server.Implementations.Library
{
IncludeItemTypes = [BaseItemKind.MusicArtist],
Name = name,
+ UseRawName = true,
DtoOptions = options
}).Cast<MusicArtist>()
.OrderBy(i => i.IsAccessedByName ? 1 : 0)
@@ -2201,6 +2202,12 @@ namespace Emby.Server.Implementations.Library
public Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
=> UpdateItemsAsync([item], parent, updateReason, cancellationToken);
+ /// <inheritdoc />
+ public async Task ReattachUserDataAsync(BaseItem item, CancellationToken cancellationToken)
+ {
+ await _itemRepository.ReattachUserDataAsync(item, cancellationToken).ConfigureAwait(false);
+ }
+
public async Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason)
{
if (item.IsFileProtocol)
@@ -3194,19 +3201,7 @@ namespace Emby.Server.Implementations.Library
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
- var shortcutFilename = Path.GetFileNameWithoutExtension(path);
-
- var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
-
- while (File.Exists(lnk))
- {
- shortcutFilename += "1";
- lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
- }
-
- _fileSystem.CreateShortcut(lnk, _appHost.ReverseVirtualPath(path));
-
- RemoveContentTypeOverrides(path);
+ CreateShortcut(virtualFolderPath, pathInfo);
if (saveLibraryOptions)
{
@@ -3371,5 +3366,24 @@ namespace Emby.Server.Implementations.Library
return item is UserRootFolder || item.IsVisibleStandalone(user);
}
+
+ public void CreateShortcut(string virtualFolderPath, MediaPathInfo pathInfo)
+ {
+ var path = pathInfo.Path;
+ var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
+
+ var shortcutFilename = Path.GetFileNameWithoutExtension(path);
+
+ var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
+
+ while (File.Exists(lnk))
+ {
+ shortcutFilename += "1";
+ lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
+ }
+
+ _fileSystem.CreateShortcut(lnk, _appHost.ReverseVirtualPath(path));
+ RemoveContentTypeOverrides(path);
+ }
}
}
diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json
index 24ed116f3..d09a7884e 100644
--- a/Emby.Server.Implementations/Localization/Core/ar.json
+++ b/Emby.Server.Implementations/Localization/Core/ar.json
@@ -2,13 +2,13 @@
"Albums": "ألبومات",
"AppDeviceValues": "تطبيق: {0}, جهاز: {1}",
"Application": "تطبيق",
- "Artists": "الفنانون",
+ "Artists": "فنانون",
"AuthenticationSucceededWithUserName": "نجحت عملية التوثيق بـ {0}",
"Books": "الكتب",
"CameraImageUploadedFrom": "رُفعت صورة الكاميرا الجديدة من {0}",
"Channels": "القنوات",
"ChapterNameValue": "الفصل {0}",
- "Collections": "المجموعات",
+ "Collections": "مجموعات",
"DeviceOfflineWithName": "قُطِع الاتصال ب{0}",
"DeviceOnlineWithName": "{0} متصل",
"FailedLoginAttemptWithUserName": "محاولة تسجيل الدخول فاشلة من {0}",
@@ -16,7 +16,7 @@
"Folders": "المجلدات",
"Genres": "التصنيفات",
"HeaderAlbumArtists": "فناني الألبوم",
- "HeaderContinueWatching": "أكمل المشاهدة",
+ "HeaderContinueWatching": "متابعة المشاهدة",
"HeaderFavoriteAlbums": "الألبومات المفضلة",
"HeaderFavoriteArtists": "الفنانون المفضلون",
"HeaderFavoriteEpisodes": "الحلقات المفضلة",
diff --git a/Emby.Server.Implementations/Localization/Core/be.json b/Emby.Server.Implementations/Localization/Core/be.json
index 29847048c..3d598c491 100644
--- a/Emby.Server.Implementations/Localization/Core/be.json
+++ b/Emby.Server.Implementations/Localization/Core/be.json
@@ -16,7 +16,7 @@
"Collections": "Калекцыі",
"Default": "Па змаўчанні",
"FailedLoginAttemptWithUserName": "Няўдалая спроба ўваходу з {0}",
- "Folders": "Тэчкі",
+ "Folders": "Папкі",
"Favorites": "Абранае",
"External": "Знешні",
"Genres": "Жанры",
@@ -50,7 +50,7 @@
"User": "Карыстальнік",
"UserDeletedWithName": "Карыстальнік {0} быў выдалены",
"UserDownloadingItemWithValues": "{0} спампоўваецца {1}",
- "TaskOptimizeDatabase": "Аптымізаваць базу дадзеных",
+ "TaskOptimizeDatabase": "Аптымізацыя базы даных",
"Artists": "Выканаўцы",
"UserOfflineFromDevice": "{0} адлучыўся ад {1}",
"UserPolicyUpdatedWithName": "Палітыка карыстальніка абноўлена для {0}",
@@ -59,8 +59,8 @@
"TaskCleanLogsDescription": "Выдаляе файлы журналу, якім больш за {0} дзён.",
"TaskUpdatePluginsDescription": "Спампоўвае і ўсталёўвае абнаўленні для плагінаў, якія сканфігураваныя на аўтаматычнае абнаўленне.",
"TaskRefreshChannelsDescription": "Абнаўляе інфармацыю аб інтэрнэт-канале.",
- "TaskDownloadMissingSubtitlesDescription": "Шукае ў інтэрнэце адсутныя субцітры на аснове канфігурацыі метададзеных.",
- "TaskOptimizeDatabaseDescription": "Ушчыльняе базу дадзеных і скарачае вольную прастору. Выкананне гэтай задачы пасля сканавання бібліятэкі або ўнясення іншых зменаў, якія прадугледжваюць мадыфікацыю базы дадзеных, можа палепшыць выдайнасць.",
+ "TaskDownloadMissingSubtitlesDescription": "Шукае ў інтэрнэце адсутныя субцітры на аснове канфігурацыі метаданых.",
+ "TaskOptimizeDatabaseDescription": "Сціскае базу даных і вызваляе вольную прастору. Выкананне гэтай задачы пасля сканіравання бібліятэкі або іншых змяненняў, якія мадыфікуюць базу даных, можа палепшыць прадукцыйнасць.",
"TaskKeyframeExtractor": "Экстрактар ключавых кадраў",
"TasksApplicationCategory": "Праграма",
"AppDeviceValues": "Праграма: {0}, Прылада: {1}",
@@ -95,7 +95,7 @@
"ServerNameNeedsToBeRestarted": "{0} патрабуе перазапуску",
"Shows": "Шоу",
"StartupEmbyServerIsLoading": "Jellyfin Server загружаецца. Калі ласка, паўтарыце спробу крыху пазней.",
- "SubtitleDownloadFailureFromForItem": "Не атрымалася спампаваць субтытры з {0} для {1}",
+ "SubtitleDownloadFailureFromForItem": "Субцітры для {1} не ўдалося спампаваць з {0}",
"TvShows": "Тэлепраграма",
"Undefined": "Нявызначана",
"UserLockedOutWithName": "Карыстальнік {0} быў заблакіраваны",
@@ -104,7 +104,7 @@
"UserStartedPlayingItemWithValues": "{0} прайграваецца {1} на {2}",
"UserStoppedPlayingItemWithValues": "{0} скончыў прайграванне {1} на {2}",
"ValueHasBeenAddedToLibrary": "{0} быў дададзены ў вашу медыятэку",
- "ValueSpecialEpisodeName": "Спецэпізод - {0}",
+ "ValueSpecialEpisodeName": "Спецвыпуск - {0}",
"VersionNumber": "Версія {0}",
"TasksMaintenanceCategory": "Абслугоўванне",
"TasksLibraryCategory": "Бібліятэка",
@@ -114,7 +114,7 @@
"TaskCleanCacheDescription": "Выдаляе файлы кэша, якія больш не патрэбныя сістэме.",
"TaskRefreshChapterImages": "Вынуць выявы раздзелаў",
"TaskRefreshLibrary": "Сканаваць бібліятэку",
- "TaskRefreshLibraryDescription": "Скануе вашу медыятэку на наяўнасць новых файлаў і абнаўляе метададзеныя.",
+ "TaskRefreshLibraryDescription": "Сканіруе вашу медыятэку на наяўнасць новых файлаў і абнаўляе метаданыя.",
"TaskCleanLogs": "Ачысціць журнал",
"TaskRefreshPeople": "Абнавіць выканаўцаў",
"TaskRefreshPeopleDescription": "Абнаўленне метаданых для акцёраў і рэжысёраў у вашай медыятэцы.",
@@ -136,6 +136,6 @@
"TaskDownloadMissingLyricsDescription": "Спампоўвае тэксты для песняў",
"TaskExtractMediaSegments": "Сканіраванне медыя-сегмента",
"TaskMoveTrickplayImages": "Перанесці месцазнаходжанне выявы Trickplay",
- "CleanupUserDataTask": "Задача па ачыстцы дадзеных карыстальніка",
- "CleanupUserDataTaskDescription": "Ачысьціць усе дадзеныя карыстальніка (стан прагляду, абранае і г.д.) для медыяфайлаў, што адсутнічаюць больш за 90 дзён."
+ "CleanupUserDataTask": "Задача па ачыстцы даных карыстальніка",
+ "CleanupUserDataTaskDescription": "Ачышчае ўсе даныя карыстальніка (стан прагляду, абранае і г.д.) для медыяфайлаў, што адсутнічаюць больш за 90 дзён."
}
diff --git a/Emby.Server.Implementations/Localization/Core/cy.json b/Emby.Server.Implementations/Localization/Core/cy.json
index 794a8e4ce..d9ebd13f0 100644
--- a/Emby.Server.Implementations/Localization/Core/cy.json
+++ b/Emby.Server.Implementations/Localization/Core/cy.json
@@ -8,7 +8,7 @@
"CameraImageUploadedFrom": "Mae delwedd camera newydd wedi'i lanlwytho o {0}",
"Books": "Llyfrau",
"AuthenticationSucceededWithUserName": "{0} wedi’i ddilysu’n llwyddiannus",
- "Artists": "Artistiaid",
+ "Artists": "Crewyr",
"AppDeviceValues": "Ap: {0}, Dyfais: {1}",
"Albums": "Albwmau",
"Genres": "Genres",
@@ -67,7 +67,7 @@
"NotificationOptionAudioPlayback": "Dechreuwyd chwarae sain",
"MessageServerConfigurationUpdated": "Mae gosodiadau gweinydd wedi'i ddiweddaru",
"MessageNamedServerConfigurationUpdatedWithValue": "Mae adran gosodiadau gweinydd {0} wedi'i diweddaru",
- "FailedLoginAttemptWithUserName": "Cais mewngofnodi wedi methu gan {0}",
+ "FailedLoginAttemptWithUserName": "Cais mewngofnodi wedi methu o {0}",
"ValueHasBeenAddedToLibrary": "{0} wedi'i hychwanegu at eich llyfrgell gyfryngau",
"UserStoppedPlayingItemWithValues": "{0} wedi gorffen chwarae {1} ar {2}",
"UserStartedPlayingItemWithValues": "{0} yn chwarae {1} ar {2}",
@@ -123,5 +123,14 @@
"TaskRefreshChapterImages": "Echdynnu Lluniau Pennod",
"TaskCleanCacheDescription": "Dileu ffeiliau cache nad oes eu hangen ar y system mwyach.",
"TaskCleanCache": "Gwaghau Ffolder Cache",
- "HearingImpaired": "Nam ar y clyw"
+ "HearingImpaired": "Nam ar y clyw",
+ "TaskAudioNormalization": "Gwastatau Sain",
+ "TaskAudioNormalizationDescription": "Yn sganio ffeiliau am ddata gwastatau sain.",
+ "TaskRefreshTrickplayImages": "Creuwch lluniau Trickplay",
+ "TaskRefreshTrickplayImagesDescription": "Creu rhagolygon Trickplay ar gyfer fideos mewn llyfrgelloedd gweithredol.",
+ "TaskDownloadMissingLyrics": "Lawrlwytho geiriau coll",
+ "TaskDownloadMissingLyricsDescription": "Lawrlwytho geiriau caneuon",
+ "TaskCleanCollectionsAndPlaylists": "Glanhau casgliadau a rhestrau chwarae",
+ "TaskCleanCollectionsAndPlaylistsDescription": "Dileu eitemau o gasgliadau a rhestrau chwarae sydd ddim yn bodoli bellach.",
+ "TaskExtractMediaSegments": "Sganio Darnau Cyfryngau"
}
diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json
index 8d86b2da1..0b042c8fe 100644
--- a/Emby.Server.Implementations/Localization/Core/de.json
+++ b/Emby.Server.Implementations/Localization/Core/de.json
@@ -61,7 +61,7 @@
"NotificationOptionVideoPlayback": "Video wird abgespielt",
"NotificationOptionVideoPlaybackStopped": "Videowiedergabe gestoppt",
"Photos": "Fotos",
- "Playlists": "Playlists",
+ "Playlists": "Wiedergabelisten",
"Plugin": "Plugin",
"PluginInstalledWithName": "{0} wurde installiert",
"PluginUninstalledWithName": "{0} wurde deinstalliert",
@@ -96,20 +96,20 @@
"TaskDownloadMissingSubtitles": "Fehlende Untertitel herunterladen",
"TaskRefreshChannelsDescription": "Aktualisiert Internet-Kanal-Informationen.",
"TaskRefreshChannels": "Kanäle aktualisieren",
- "TaskCleanTranscodeDescription": "Löscht temporäre Videodateien, die älter als 24 Stunden sind.",
- "TaskCleanTranscode": "Temporäre Videodateien löschen",
+ "TaskCleanTranscodeDescription": "Löscht Transkodierungsdateien, die älter als einen Tag sind.",
+ "TaskCleanTranscode": "Transkodierungsverzeichnis leeren",
"TaskUpdatePluginsDescription": "Lädt Updates für Plugins herunter, welche für automatische Updates konfiguriert sind und installiert diese.",
"TaskUpdatePlugins": "Plugins aktualisieren",
"TaskRefreshPeopleDescription": "Aktualisiert Metadaten für Schauspieler und Regisseure in deinen Bibliotheken.",
"TaskRefreshPeople": "Personen aktualisieren",
"TaskCleanLogsDescription": "Lösche Log-Dateien, die älter als {0} Tage sind.",
- "TaskCleanLogs": "Alte Protokolle löschen",
- "TaskRefreshLibraryDescription": "Sucht nach neuen Dateien und aktualisiert Infos zu deinen Medien.",
+ "TaskCleanLogs": "Protokollverzeichnis leeren",
+ "TaskRefreshLibraryDescription": "Durchsucht deine Medienbibliothek nach neuen Dateien und aktualisiert Metadaten.",
"TaskRefreshLibrary": "Medien-Bibliothek scannen",
"TaskRefreshChapterImagesDescription": "Erstellt Vorschaubilder für Videokapitel.",
"TaskRefreshChapterImages": "Kapitelvorschauen erstellen",
- "TaskCleanCacheDescription": "Entfernt nicht mehr benötigte Cache-Dateien.",
- "TaskCleanCache": "Cache leeren",
+ "TaskCleanCacheDescription": "Löscht Cache-Dateien, die vom System nicht mehr benötigt werden.",
+ "TaskCleanCache": "Cache-Verzeichnis leeren",
"TasksChannelsCategory": "Internet-Kanäle",
"TasksApplicationCategory": "Anwendung",
"TasksLibraryCategory": "Bibliothek",
diff --git a/Emby.Server.Implementations/Localization/Core/et.json b/Emby.Server.Implementations/Localization/Core/et.json
index 2e692009b..91a0aa663 100644
--- a/Emby.Server.Implementations/Localization/Core/et.json
+++ b/Emby.Server.Implementations/Localization/Core/et.json
@@ -72,7 +72,7 @@
"NotificationOptionApplicationUpdateAvailable": "Rakenduse uuendus on saadaval",
"NewVersionIsAvailable": "Jellyfin serveri uus versioon on allalaadimiseks saadaval.",
"NameSeasonUnknown": "Tundmatu hooaeg",
- "NameSeasonNumber": "Hooaeg {0}",
+ "NameSeasonNumber": "{0}. hooaeg",
"NameInstallFailed": "{0} paigaldamine nurjus",
"MusicVideos": "Muusikavideod",
"Music": "Muusika",
diff --git a/Emby.Server.Implementations/Localization/Core/ml.json b/Emby.Server.Implementations/Localization/Core/ml.json
index 5c3449381..8c20ded3a 100644
--- a/Emby.Server.Implementations/Localization/Core/ml.json
+++ b/Emby.Server.Implementations/Localization/Core/ml.json
@@ -2,12 +2,12 @@
"AppDeviceValues": "അപ്ലിക്കേഷൻ: {0}, ഉപകരണം: {1}",
"Application": "അപ്ലിക്കേഷൻ",
"AuthenticationSucceededWithUserName": "{0} വിജയകരമായി പ്രാമാണീകരിച്ചു",
- "CameraImageUploadedFrom": "Camera 0 from എന്നതിൽ നിന്ന് ഒരു പുതിയ ക്യാമറ ചിത്രം അപ്‌ലോഡുചെയ്‌തു",
+ "CameraImageUploadedFrom": "{0} എന്നതിൽ നിന്ന് ഒരു പുതിയ ക്യാമറ ചിത്രം അപ്‌ലോഡുചെയ്‌തു",
"ChapterNameValue": "അധ്യായം {0}",
"DeviceOfflineWithName": "{0} വിച്ഛേദിച്ചു",
"DeviceOnlineWithName": "{0} ബന്ധിപ്പിച്ചു",
"FailedLoginAttemptWithUserName": "{0}ൽ നിന്നുള്ള പ്രവേശന ശ്രമം പരാജയപ്പെട്ടു",
- "Forced": "നിർബന്ധിച്ചു",
+ "Forced": "നിർബന്ധിതമായി",
"HeaderFavoriteAlbums": "പ്രിയപ്പെട്ട ആൽബങ്ങൾ",
"HeaderFavoriteArtists": "പ്രിയപ്പെട്ട കലാകാരന്മാർ",
"HeaderFavoriteEpisodes": "പ്രിയപ്പെട്ട എപ്പിസോഡുകൾ",
@@ -114,7 +114,7 @@
"Artists": "കലാകാരന്മാർ",
"Shows": "ഷോകൾ",
"Default": "സ്ഥിരസ്ഥിതി",
- "Favorites": "പ്രിയങ്കരങ്ങൾ",
+ "Favorites": "പ്രിയപ്പെട്ടവ",
"Books": "പുസ്തകങ്ങൾ",
"Genres": "വിഭാഗങ്ങൾ",
"Channels": "ചാനലുകൾ",
diff --git a/Emby.Server.Implementations/Localization/Core/my.json b/Emby.Server.Implementations/Localization/Core/my.json
index 4cb4cdc75..097d0d2fb 100644
--- a/Emby.Server.Implementations/Localization/Core/my.json
+++ b/Emby.Server.Implementations/Localization/Core/my.json
@@ -126,5 +126,7 @@
"TaskRefreshTrickplayImages": "ထရစ်ခ်ပလေး ပုံများကို ထုတ်မည်",
"TaskKeyframeExtractor": "ကီးဖရိန်များကို ထုတ်နုတ်ခြင်း",
"TaskCleanCollectionsAndPlaylists": "စုစည်းမှုများနှင့် အစဉ်လိုက်ပြသမှုများကို ရှင်းလင်းမည်",
- "HearingImpaired": "အကြားအာရုံ ချို့တဲ့သူ"
+ "HearingImpaired": "အကြားအာရုံ ချို့တဲ့သူ",
+ "TaskDownloadMissingLyrics": "ကျန်နေသောသီချင်းစာသားများအား ဒေါင်းလုတ်ဆွဲပါ",
+ "TaskDownloadMissingLyricsDescription": "သီချင်းများအတွက် သီချင်းစာသား ဒေါင်းလုတ်ဆွဲပါ"
}
diff --git a/Emby.Server.Implementations/Localization/Core/th.json b/Emby.Server.Implementations/Localization/Core/th.json
index 113e4f30f..65ddb55e9 100644
--- a/Emby.Server.Implementations/Localization/Core/th.json
+++ b/Emby.Server.Implementations/Localization/Core/th.json
@@ -135,5 +135,7 @@
"TaskExtractMediaSegments": "การสแกนส่วนของสื่อมีเดีย",
"TaskMoveTrickplayImagesDescription": "ย้ายไฟล์ Trickplay ตามการตั้งค่าของไลบรารี",
"TaskExtractMediaSegmentsDescription": "แยกหรือดึงส่วนของสื่อจากปลั๊กอินที่เปิดใช้งาน MediaSegment",
- "TaskMoveTrickplayImages": "ย้ายตำแหน่งเก็บภาพตัวอย่าง Trickplay"
+ "TaskMoveTrickplayImages": "ย้ายตำแหน่งเก็บภาพตัวอย่าง Trickplay",
+ "CleanupUserDataTask": "ส่วนงานล้างข้อมูลผู้ใช้",
+ "CleanupUserDataTaskDescription": "ล้างข้อมูลผู้ใช้ทั้งหมด (สถานะการรับชม สถานะรายการโปรด ฯลฯ) จากสื่อที่ไม่ได้ใช้งานแล้วอย่างน้อย 90 วัน"
}
diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json
index 478111049..a07e6864e 100644
--- a/Emby.Server.Implementations/Localization/Core/tr.json
+++ b/Emby.Server.Implementations/Localization/Core/tr.json
@@ -30,7 +30,7 @@
"ItemAddedWithName": "{0} kütüphaneye eklendi",
"ItemRemovedWithName": "{0} kütüphaneden silindi",
"LabelIpAddressValue": "IP adresi: {0}",
- "LabelRunningTimeValue": "Çalışma süresi: {0}",
+ "LabelRunningTimeValue": "Oynatma süresi: {0}",
"Latest": "En son",
"MessageApplicationUpdated": "Jellyfin Sunucusu güncellendi",
"MessageApplicationUpdatedTo": "Jellyfin Sunucusu {0} sürümüne güncellendi",
@@ -42,7 +42,7 @@
"MusicVideos": "Müzik Videoları",
"NameInstallFailed": "{0} kurulumu başarısız",
"NameSeasonNumber": "{0}. Sezon",
- "NameSeasonUnknown": "Bilinmeyen Sezon",
+ "NameSeasonUnknown": "Sezon Bilinmiyor",
"NewVersionIsAvailable": "Jellyfin Sunucusunun yeni bir sürümü indirmek için hazır.",
"NotificationOptionApplicationUpdateAvailable": "Uygulama güncellemesi mevcut",
"NotificationOptionApplicationUpdateInstalled": "Uygulama güncellemesi yüklendi",
@@ -57,7 +57,7 @@
"NotificationOptionPluginUpdateInstalled": "Eklenti güncellemesi yüklendi",
"NotificationOptionServerRestartRequired": "Sunucunun yeniden başlatılması gerekiyor",
"NotificationOptionTaskFailed": "Zamanlanmış görev hatası",
- "NotificationOptionUserLockedOut": "Kullanıcı kilitlendi",
+ "NotificationOptionUserLockedOut": "Kullanıcı hesabı kilitlendi",
"NotificationOptionVideoPlayback": "Video oynatma başladı",
"NotificationOptionVideoPlaybackStopped": "Video oynatma durduruldu",
"Photos": "Fotoğraflar",
@@ -74,7 +74,7 @@
"Songs": "Şarkılar",
"StartupEmbyServerIsLoading": "Jellyfin Sunucusu yükleniyor. Lütfen kısa süre sonra tekrar deneyin.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
- "SubtitleDownloadFailureFromForItem": "{1} için alt yazılar {0} sağlayıcısından indirilemedi",
+ "SubtitleDownloadFailureFromForItem": "{1} için altyazılar {0} sağlayıcısından indirilemedi",
"Sync": "Eşzamanlama",
"System": "Sistem",
"TvShows": "Diziler",
@@ -82,7 +82,7 @@
"UserCreatedWithName": "{0} kullanıcısı oluşturuldu",
"UserDeletedWithName": "{0} kullanıcısı silindi",
"UserDownloadingItemWithValues": "{0} kullanıcısı {1} medyasını indiriyor",
- "UserLockedOutWithName": "{0} adlı kullanıcı kilitlendi",
+ "UserLockedOutWithName": "{0} adlı kullanıcı hesabı kilitlendi",
"UserOfflineFromDevice": "{0} kullanıcısının {1} ile bağlantısı kesildi",
"UserOnlineFromDevice": "{0} kullanıcısı {1} ile çevrimiçi",
"UserPasswordChangedWithName": "{0} kullanıcısının parolası değiştirildi",
@@ -98,8 +98,8 @@
"TasksLibraryCategory": "Kütüphane",
"TasksMaintenanceCategory": "Bakım",
"TaskRefreshPeopleDescription": "Medya kütüphanenizdeki videoların oyuncu ve yönetmen bilgilerini günceller.",
- "TaskDownloadMissingSubtitlesDescription": "Meta veri yapılandırmasına dayalı olarak eksik alt yazılar için internette arama yapar.",
- "TaskDownloadMissingSubtitles": "Eksik alt yazıları indir",
+ "TaskDownloadMissingSubtitlesDescription": "Meta veri yapılandırmasına dayalı olarak eksik altyazılar için internette arama yapar.",
+ "TaskDownloadMissingSubtitles": "Eksik altyazıları indir",
"TaskRefreshChannelsDescription": "Internet kanal bilgilerini yenile.",
"TaskRefreshChannels": "Kanalları Yenile",
"TaskCleanTranscodeDescription": "Bir günden daha eski kod dönüştürme dosyalarını siler.",
@@ -125,15 +125,15 @@
"TaskKeyframeExtractor": "Ana Kare Çıkarıcı",
"External": "Harici",
"HearingImpaired": "Duyma Engelli",
- "TaskRefreshTrickplayImages": "Trickplay Görselleri Oluştur",
- "TaskRefreshTrickplayImagesDescription": "Etkin kütüphanelerdeki videolar için trickplay önizlemeleri oluşturur.",
+ "TaskRefreshTrickplayImages": "Hızlı Önizleme Görsellerini Oluştur",
+ "TaskRefreshTrickplayImagesDescription": "Etkin kütüphanelerdeki videolar için hızlı önizleme görselleri oluşturur.",
"TaskCleanCollectionsAndPlaylistsDescription": "Artık var olmayan koleksiyon ve çalma listelerindeki ögeleri kaldırır.",
"TaskCleanCollectionsAndPlaylists": "Koleksiyonları ve çalma listelerini temizleyin",
"TaskAudioNormalizationDescription": "Ses normalleştirme verileri için dosyaları tarar.",
"TaskAudioNormalization": "Ses Normalleştirme",
"TaskExtractMediaSegments": "Medya Segmenti Tarama",
- "TaskMoveTrickplayImages": "Trickplay Görsel Konumunu Taşıma",
- "TaskMoveTrickplayImagesDescription": "Mevcut trickplay dosyalarını kütüphane ayarlarına göre taşır.",
+ "TaskMoveTrickplayImages": "Hızlı Önizleme Görsel Konumunu Taşıma",
+ "TaskMoveTrickplayImagesDescription": "Mevcut hızlı önizleme dosyalarını kütüphane ayarlarına göre taşır.",
"TaskDownloadMissingLyrics": "Eksik şarkı sözlerini indir",
"TaskDownloadMissingLyricsDescription": "Şarkı sözlerini indirir",
"TaskExtractMediaSegmentsDescription": "MediaSegment özelliği etkin olan eklentilerden medya segmentlerini çıkarır veya alır.",
diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json
index 1bfa4e3c3..b9635105a 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-CN.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json
@@ -5,60 +5,60 @@
"Artists": "艺术家",
"AuthenticationSucceededWithUserName": "{0} 认证成功",
"Books": "书籍",
- "CameraImageUploadedFrom": "新的相机图像已从 {0} 上传",
+ "CameraImageUploadedFrom": "已从 {0} 上传新的相机照片",
"Channels": "频道",
"ChapterNameValue": "章节 {0}",
"Collections": "合集",
- "DeviceOfflineWithName": "{0} 已断开",
+ "DeviceOfflineWithName": "{0} 已断开连接",
"DeviceOnlineWithName": "{0} 已连接",
- "FailedLoginAttemptWithUserName": "来自 {0} 的登录尝试失败",
- "Favorites": "我的最爱",
+ "FailedLoginAttemptWithUserName": "来自 {0} 的登录失败",
+ "Favorites": "收藏夹",
"Folders": "文件夹",
"Genres": "类型",
"HeaderAlbumArtists": "专辑艺术家",
"HeaderContinueWatching": "继续观看",
"HeaderFavoriteAlbums": "收藏的专辑",
- "HeaderFavoriteArtists": "最爱的艺术家",
- "HeaderFavoriteEpisodes": "最爱的剧集",
- "HeaderFavoriteShows": "最爱的节目",
- "HeaderFavoriteSongs": "最爱的歌曲",
+ "HeaderFavoriteArtists": "收藏的艺术家",
+ "HeaderFavoriteEpisodes": "收藏的剧集",
+ "HeaderFavoriteShows": "收藏的节目",
+ "HeaderFavoriteSongs": "收藏的歌曲",
"HeaderLiveTV": "电视直播",
- "HeaderNextUp": "接下来",
+ "HeaderNextUp": "接下来播放",
"HeaderRecordingGroups": "录制组",
"HomeVideos": "家庭视频",
"Inherit": "继承",
"ItemAddedWithName": "{0} 已添加到媒体库",
- "ItemRemovedWithName": "{0} 已从媒体库中移除",
+ "ItemRemovedWithName": "{0} 已从媒体库移除",
"LabelIpAddressValue": "IP 地址:{0}",
"LabelRunningTimeValue": "运行时间:{0}",
"Latest": "最新",
"MessageApplicationUpdated": "Jellyfin 服务器已更新",
- "MessageApplicationUpdatedTo": "Jellyfin Server 版本已更新为 {0}",
+ "MessageApplicationUpdatedTo": "Jellyfin 服务器版本已更新到 {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "服务器配置 {0} 部分已更新",
"MessageServerConfigurationUpdated": "服务器配置已更新",
"MixedContent": "混合内容",
"Movies": "电影",
"Music": "音乐",
- "MusicVideos": "音乐视频",
+ "MusicVideos": "MV",
"NameInstallFailed": "{0} 安装失败",
"NameSeasonNumber": "第 {0} 季",
"NameSeasonUnknown": "未知季",
- "NewVersionIsAvailable": "Jellyfin Server 有新版本可以下载。",
+ "NewVersionIsAvailable": "Jellyfin 服务器有新版本可供下载。",
"NotificationOptionApplicationUpdateAvailable": "有可用的应用程序更新",
"NotificationOptionApplicationUpdateInstalled": "应用程序更新已安装",
- "NotificationOptionAudioPlayback": "音频开始播放",
+ "NotificationOptionAudioPlayback": "音频已开始播放",
"NotificationOptionAudioPlaybackStopped": "音频播放已停止",
- "NotificationOptionCameraImageUploaded": "相机图片已上传",
+ "NotificationOptionCameraImageUploaded": "相机照片已上传",
"NotificationOptionInstallationFailed": "安装失败",
"NotificationOptionNewLibraryContent": "已添加新内容",
- "NotificationOptionPluginError": "插件失败",
+ "NotificationOptionPluginError": "插件出错",
"NotificationOptionPluginInstalled": "插件已安装",
"NotificationOptionPluginUninstalled": "插件已卸载",
- "NotificationOptionPluginUpdateInstalled": "插件更新已安装",
+ "NotificationOptionPluginUpdateInstalled": "插件已更新",
"NotificationOptionServerRestartRequired": "服务器需要重启",
"NotificationOptionTaskFailed": "计划任务失败",
- "NotificationOptionUserLockedOut": "用户已锁定",
- "NotificationOptionVideoPlayback": "视频开始播放",
+ "NotificationOptionUserLockedOut": "用户已被锁定",
+ "NotificationOptionVideoPlayback": "视频已开始播放",
"NotificationOptionVideoPlaybackStopped": "视频播放已停止",
"Photos": "照片",
"Playlists": "播放列表",
@@ -72,23 +72,23 @@
"ServerNameNeedsToBeRestarted": "{0} 需要重新启动",
"Shows": "节目",
"Songs": "歌曲",
- "StartupEmbyServerIsLoading": "Jellyfin 服务器加载中。请稍后再试。",
+ "StartupEmbyServerIsLoading": "Jellyfin 服务器正在启动,请稍后再试。",
"SubtitleDownloadFailureForItem": "为 {0} 下载字幕失败",
"SubtitleDownloadFailureFromForItem": "无法从 {0} 下载 {1} 的字幕",
"Sync": "同步",
"System": "系统",
"TvShows": "电视剧",
"User": "用户",
- "UserCreatedWithName": "用户 {0} 已创建",
- "UserDeletedWithName": "用户 {0} 已删除",
+ "UserCreatedWithName": "已创建用户 {0}",
+ "UserDeletedWithName": "已删除用户 {0}",
"UserDownloadingItemWithValues": "{0} 正在下载 {1}",
"UserLockedOutWithName": "用户 {0} 已被锁定",
"UserOfflineFromDevice": "{0} 已从 {1} 断开",
- "UserOnlineFromDevice": "{0} 在线,来自 {1}",
- "UserPasswordChangedWithName": "已为用户 {0} 更改密码",
- "UserPolicyUpdatedWithName": "用户协议已经被更新为 {0}",
- "UserStartedPlayingItemWithValues": "{0} 已在 {2} 上开始播放 {1}",
- "UserStoppedPlayingItemWithValues": "{0} 已在 {2} 上停止播放 {1}",
+ "UserOnlineFromDevice": "{0} 已在 {1} 上线",
+ "UserPasswordChangedWithName": "用户 {0} 的密码已更改",
+ "UserPolicyUpdatedWithName": "用户协议已更新为 {0}",
+ "UserStartedPlayingItemWithValues": "{0} 在 {2} 上开始播放 {1}",
+ "UserStoppedPlayingItemWithValues": "{0} 在 {2} 上停止播放 {1}",
"ValueHasBeenAddedToLibrary": "{0} 已添加至您的媒体库中",
"ValueSpecialEpisodeName": "特典 - {0}",
"VersionNumber": "版本 {0}",
diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs
index b4c65ad85..bc80c2b40 100644
--- a/Emby.Server.Implementations/Localization/LocalizationManager.cs
+++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs
@@ -38,6 +38,7 @@ namespace Emby.Server.Implementations.Localization
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
+ private readonly ConcurrentDictionary<string, CultureDto?> _cultureCache = new(StringComparer.OrdinalIgnoreCase);
private List<CultureDto> _cultures = [];
private FrozenDictionary<string, string> _iso6392BtoT = null!;
@@ -161,6 +162,7 @@ namespace Emby.Server.Implementations.Localization
list.Add(new CultureDto(name, displayname, twoCharName, threeLetterNames));
}
+ _cultureCache.Clear();
_cultures = list;
_iso6392BtoT = iso6392BtoTdict.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
}
@@ -169,20 +171,31 @@ namespace Emby.Server.Implementations.Localization
/// <inheritdoc />
public CultureDto? FindLanguageInfo(string language)
{
- // TODO language should ideally be a ReadOnlySpan but moq cannot mock ref structs
- for (var i = 0; i < _cultures.Count; i++)
+ if (string.IsNullOrEmpty(language))
{
- var culture = _cultures[i];
- if (language.Equals(culture.DisplayName, StringComparison.OrdinalIgnoreCase)
- || language.Equals(culture.Name, StringComparison.OrdinalIgnoreCase)
- || culture.ThreeLetterISOLanguageNames.Contains(language, StringComparison.OrdinalIgnoreCase)
- || language.Equals(culture.TwoLetterISOLanguageName, StringComparison.OrdinalIgnoreCase))
- {
- return culture;
- }
+ return null;
}
- return default;
+ return _cultureCache.GetOrAdd(
+ language,
+ static (lang, cultures) =>
+ {
+ // TODO language should ideally be a ReadOnlySpan but moq cannot mock ref structs
+ for (var i = 0; i < cultures.Count; i++)
+ {
+ var culture = cultures[i];
+ if (lang.Equals(culture.DisplayName, StringComparison.OrdinalIgnoreCase)
+ || lang.Equals(culture.Name, StringComparison.OrdinalIgnoreCase)
+ || culture.ThreeLetterISOLanguageNames.Contains(lang, StringComparison.OrdinalIgnoreCase)
+ || lang.Equals(culture.TwoLetterISOLanguageName, StringComparison.OrdinalIgnoreCase))
+ {
+ return culture;
+ }
+ }
+
+ return null;
+ },
+ _cultures);
}
/// <inheritdoc />
@@ -311,15 +324,19 @@ namespace Emby.Server.Implementations.Localization
else
{
// Fall back to server default language for ratings check
- // If it has no ratings, use the US ratings
- var ratingsDictionary = GetParentalRatingsDictionary() ?? GetParentalRatingsDictionary("us");
+ var ratingsDictionary = GetParentalRatingsDictionary();
if (ratingsDictionary is not null && ratingsDictionary.TryGetValue(rating, out ParentalRatingScore? value))
{
return value;
}
}
- // If we don't find anything, check all ratings systems
+ // If we don't find anything, check all ratings systems, starting with US
+ if (_allParentalRatings.TryGetValue("us", out var usRatings) && usRatings.TryGetValue(rating, out var usValue))
+ {
+ return usValue;
+ }
+
foreach (var dictionary in _allParentalRatings.Values)
{
if (dictionary.TryGetValue(rating, out var value))
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index cf2ca047c..bbe23f8df 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -793,6 +793,16 @@ namespace Emby.Server.Implementations.Session
PlaySessionId = info.PlaySessionId
};
+ if (info.Item is not null)
+ {
+ _logger.LogInformation(
+ "User {0} started playback of '{1}' ({2} {3})",
+ session.UserName,
+ info.Item.Name,
+ session.Client,
+ session.ApplicationVersion);
+ }
+
await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false);
// Nothing to save here
@@ -1060,11 +1070,12 @@ namespace Emby.Server.Implementations.Session
var msString = info.PositionTicks.HasValue ? (info.PositionTicks.Value / 10000).ToString(CultureInfo.InvariantCulture) : "unknown";
_logger.LogInformation(
- "Playback stopped reported by app {0} {1} playing {2}. Stopped at {3} ms",
- session.Client,
- session.ApplicationVersion,
+ "User {0} stopped playback of '{1}' at {2}ms ({3} {4})",
+ session.UserName,
info.Item.Name,
- msString);
+ msString,
+ session.Client,
+ session.ApplicationVersion);
}
if (info.NowPlayingQueue is not null)
diff --git a/Emby.Server.Implementations/Sorting/StudioComparer.cs b/Emby.Server.Implementations/Sorting/StudioComparer.cs
index 0edffb783..6d041cf11 100644
--- a/Emby.Server.Implementations/Sorting/StudioComparer.cs
+++ b/Emby.Server.Implementations/Sorting/StudioComparer.cs
@@ -1,11 +1,11 @@
#pragma warning disable CS1591
using System;
+using System.Globalization;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
-using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
@@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Sorting
ArgumentNullException.ThrowIfNull(x);
ArgumentNullException.ThrowIfNull(y);
- return AlphanumericComparator.CompareValues(x.Studios.FirstOrDefault(), y.Studios.FirstOrDefault());
+ return CultureInfo.InvariantCulture.CompareInfo.Compare(x.Studios.FirstOrDefault(), y.Studios.FirstOrDefault(), CompareOptions.NumericOrdering);
}
}
}
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index 5ff400160..67b77a112 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -156,6 +156,11 @@ namespace Emby.Server.Implementations.Updates
_logger.LogError(ex, "The URL configured for the plugin repository manifest URL is not valid: {Manifest}", manifest);
return Array.Empty<PackageInfo>();
}
+ catch (NotSupportedException ex)
+ {
+ _logger.LogError(ex, "The URL scheme configured for the plugin repository is not supported: {Manifest}", manifest);
+ return Array.Empty<PackageInfo>();
+ }
catch (HttpRequestException ex)
{
_logger.LogError(ex, "An error occurred while accessing the plugin manifest: {Manifest}", manifest);
@@ -557,7 +562,7 @@ namespace Emby.Server.Implementations.Updates
}
stream.Position = 0;
- ZipFile.ExtractToDirectory(stream, targetDir, true);
+ await ZipFile.ExtractToDirectoryAsync(stream, targetDir, true, cancellationToken);
// Ensure we create one or populate existing ones with missing data.
await _pluginManager.PopulateManifest(package.PackageInfo, package.Version, targetDir, status).ConfigureAwait(false);