From 159a244654aa533a149fe00636c9c8e8fa5d2c01 Mon Sep 17 00:00:00 2001 From: SenorSmartyPants Date: Sat, 19 Nov 2022 14:14:41 -0600 Subject: Add Options to disable DVR NFO and image saving - SaveRecordingNFO and SaveRecordingImages default to true. Maintains current behavior. - Episode.FillMissingEpisodeNumbersFromPath for live tv so external metadata can be pulled when recording starts. --- .../LiveTv/EmbyTV/EmbyTV.cs | 28 +++++++++++++--------- 1 file changed, 17 insertions(+), 11 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index a0ae328a47..6739c10c1e 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1814,21 +1814,27 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV program.AddGenre("News"); } - if (timer.IsProgramSeries) + if (GetConfiguration().SaveRecordingNFO) { - await SaveSeriesNfoAsync(timer, seriesPath).ConfigureAwait(false); - await SaveVideoNfoAsync(timer, recordingPath, program, false).ConfigureAwait(false); - } - else if (!timer.IsMovie || timer.IsSports || timer.IsNews) - { - await SaveVideoNfoAsync(timer, recordingPath, program, true).ConfigureAwait(false); + if (timer.IsProgramSeries) + { + await SaveSeriesNfoAsync(timer, seriesPath).ConfigureAwait(false); + await SaveVideoNfoAsync(timer, recordingPath, program, false).ConfigureAwait(false); + } + else if (!timer.IsMovie || timer.IsSports || timer.IsNews) + { + await SaveVideoNfoAsync(timer, recordingPath, program, true).ConfigureAwait(false); + } + else + { + await SaveVideoNfoAsync(timer, recordingPath, program, false).ConfigureAwait(false); + } } - else + + if (GetConfiguration().SaveRecordingImages) { - await SaveVideoNfoAsync(timer, recordingPath, program, false).ConfigureAwait(false); + await SaveRecordingImages(recordingPath, program).ConfigureAwait(false); } - - await SaveRecordingImages(recordingPath, program).ConfigureAwait(false); } catch (Exception ex) { -- cgit v1.2.3 From 8f4ac1cb811ac4d62512149a5feb01703004b058 Mon Sep 17 00:00:00 2001 From: SenorSmartyPants Date: Sun, 27 Nov 2022 13:13:11 -0600 Subject: Call GetConfiguration just once in function --- Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 6739c10c1e..f27978a449 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1814,7 +1814,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV program.AddGenre("News"); } - if (GetConfiguration().SaveRecordingNFO) + var config = GetConfiguration(); + + if (config.SaveRecordingNFO) { if (timer.IsProgramSeries) { @@ -1831,7 +1833,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - if (GetConfiguration().SaveRecordingImages) + if (config.SaveRecordingImages) { await SaveRecordingImages(recordingPath, program).ConfigureAwait(false); } -- cgit v1.2.3 From 2385588998b9b9c562c42b122fea6c421641ed3b Mon Sep 17 00:00:00 2001 From: gam24 Date: Thu, 22 Dec 2022 23:18:48 +0000 Subject: Translated using Weblate (Polish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pl/ --- Emby.Server.Implementations/Localization/Core/pl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/pl.json b/Emby.Server.Implementations/Localization/Core/pl.json index d0b458a8fb..d4c15ac876 100644 --- a/Emby.Server.Implementations/Localization/Core/pl.json +++ b/Emby.Server.Implementations/Localization/Core/pl.json @@ -123,5 +123,6 @@ "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ść.", "External": "Zewnętrzny", "TaskKeyframeExtractorDescription": "Wyodrębnia klatki kluczowe z plików wideo w celu utworzenia bardziej precyzyjnych list odtwarzania HLS. To zadanie może trwać przez długi czas.", - "TaskKeyframeExtractor": "Ekstraktor klatek kluczowych" + "TaskKeyframeExtractor": "Ekstraktor klatek kluczowych", + "HearingImpaired": "Niedosłyszący" } -- cgit v1.2.3 From a6efdd850a551bd63cb4ca8459078352578d53e2 Mon Sep 17 00:00:00 2001 From: DuaLee Date: Sat, 24 Dec 2022 22:20:02 +0000 Subject: Translated using Weblate (Korean) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ko/ --- Emby.Server.Implementations/Localization/Core/ko.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json index a4b2e75b30..67dcf5b049 100644 --- a/Emby.Server.Implementations/Localization/Core/ko.json +++ b/Emby.Server.Implementations/Localization/Core/ko.json @@ -123,5 +123,6 @@ "TaskOptimizeDatabase": "데이터베이스 최적화", "TaskKeyframeExtractorDescription": "비디오 파일에서 키프레임을 추출하여 더 정확한 HLS 재생 목록을 만듭니다. 이 작업은 오랫동안 진행될 수 있습니다.", "TaskKeyframeExtractor": "키프레임 추출", - "External": "외부" + "External": "외부", + "HearingImpaired": "청각 장애" } -- cgit v1.2.3 From ef4ea1eb77b805b9f57a6a14b18f155630354020 Mon Sep 17 00:00:00 2001 From: 0TTA Date: Mon, 26 Dec 2022 07:00:19 +0000 Subject: Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/ --- Emby.Server.Implementations/Localization/Core/ar.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index ada3c77301..4508363b0c 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -12,7 +12,7 @@ "DeviceOfflineWithName": "قُطِع الاتصال ب{0}", "DeviceOnlineWithName": "{0} متصل", "FailedLoginAttemptWithUserName": "محاولة تسجيل الدخول فشلت من {0}", - "Favorites": "مفضلات", + "Favorites": "المفضلة", "Folders": "المجلدات", "Genres": "التصنيفات", "HeaderAlbumArtists": "فناني الألبوم", @@ -91,13 +91,13 @@ "UserStoppedPlayingItemWithValues": "قام {0} بإيقاف تشغيل {1} على {2}", "ValueHasBeenAddedToLibrary": "تمت اضافت {0} إلى مكتبة الوسائط", "ValueSpecialEpisodeName": "حلقه خاصه - {0}", - "VersionNumber": "النسخة {0}", + "VersionNumber": "الإصدار {0}", "TaskCleanCacheDescription": "يحذف الملفات المؤقتة التي لم يعد النظام بحاجة إليها.", "TaskCleanCache": "احذف ما بمجلد الملفات المؤقتة", "TasksChannelsCategory": "قنوات الإنترنت", "TasksLibraryCategory": "مكتبة", "TasksMaintenanceCategory": "صيانة", - "TaskRefreshLibraryDescription": "يفحص مكتبة الوسائط الخاصة بك باحثا عن ملفات جديدة، ومن ثم يتحدث البيانات الوصفية.", + "TaskRefreshLibraryDescription": "يفحص مكتبة الوسائط الخاصة بك باحثا عن ملفات جديدة، ومن ثم يُحدث البيانات الوصفية.", "TaskRefreshLibrary": "افحص مكتبة الوسائط", "TaskRefreshChapterImagesDescription": "يُنشئ صور مصغرة لمقاطع الفيديو التي تحتوي على فصول.", "TaskRefreshChapterImages": "استخراج صور الفصل", @@ -123,5 +123,6 @@ "TaskOptimizeDatabase": "تحسين قاعدة البيانات", "TaskKeyframeExtractorDescription": "يستخرج الإطارات الرئيسية من ملفات الفيديو لكي ينشئ قوائم تشغيل بث HTTP المباشر. قد تستمر هذه العملية لوقت طويل.", "TaskKeyframeExtractor": "مستخرج الإطار الرئيسي", - "External": "خارجي" + "External": "خارجي", + "HearingImpaired": "ضعاف السمع" } -- cgit v1.2.3 From f32d4040f23657346721ed234b9e29710adb3c9d Mon Sep 17 00:00:00 2001 From: NorwayFun Date: Mon, 26 Dec 2022 05:53:14 +0000 Subject: Translated using Weblate (Georgian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ka/ --- Emby.Server.Implementations/Localization/Core/ka.json | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/ka.json b/Emby.Server.Implementations/Localization/Core/ka.json index 3a8b89f446..dbbc81eeb2 100644 --- a/Emby.Server.Implementations/Localization/Core/ka.json +++ b/Emby.Server.Implementations/Localization/Core/ka.json @@ -108,5 +108,20 @@ "UserPasswordChangedWithName": "მომხმარებლისთვის {0} პაროლი შეცვლილია", "UserPolicyUpdatedWithName": "{0}-ის მომხმარებლის პოლიტიკა განახლდა", "UserStoppedPlayingItemWithValues": "{0}-მა დაამთავრა {1}-ის დაკვრა {2}-ზე", - "TaskRefreshChapterImagesDescription": "თავების მქონე ვიდეოებისთვის მინიატურების შექმნა." + "TaskRefreshChapterImagesDescription": "თავების მქონე ვიდეოებისთვის მინიატურების შექმნა.", + "TaskKeyframeExtractorDescription": "უფრო ზუსტი HLS დასაკრავი სიებისითვის ვიდეოდან საკვანძო გადრების ამოღება. შეიძლება საკმაო დრო დასჭირდეს.", + "NewVersionIsAvailable": "გადმოსაწერად ხელმისაწვდომია Jellyfin -ის ახალი ვერსია.", + "CameraImageUploadedFrom": "ახალი კამერის გამოსახულება ატვირთულია {0}-დან", + "StartupEmbyServerIsLoading": "Jellyfin სერვერი იტვირთება. მოგვიანებით სცადეთ.", + "SubtitleDownloadFailureFromForItem": "{0}-დან {1}-სთვის სუბტიტრების გადმოწერის შეცდომა", + "ValueHasBeenAddedToLibrary": "{0} დაემატა თქვენს მედიის ბიბლიოთეკას", + "TaskCleanActivityLogDescription": "მითითებულ ასაკზე ძველი ჟურნალის ჩანაწერების წაშლა.", + "TaskCleanCacheDescription": "სისტემისთვის არასაჭირო ქეშის ფაილების წაშლა.", + "TaskRefreshLibraryDescription": "თქვენი მედია ბიბლიოთეკაში ახალი ფაილების ძებნა და მეტამონაცემების განახლება.", + "TaskCleanLogsDescription": "{0} დღეზე ძველი ჟურნალის ფაილების წაშლა.", + "TaskRefreshPeopleDescription": "თქვენს მედიის ბიბლიოთეკაში მსახიობების და რეჟისორების მეტამონაცემების განახლება.", + "TaskUpdatePluginsDescription": "ავტომატურად განახლებადად მონიშნული დამატებების განახლებების გადმოწერა და დაყენება.", + "TaskCleanTranscodeDescription": "ერთ დღეზე უფრო ძველი ტრანსკოდირების ფაილების წაშლა.", + "TaskDownloadMissingSubtitlesDescription": "მეტამონაცემებზე დაყრდნობით ინტერნეტში ნაკლული სუბტიტრების ძებნა.", + "TaskOptimizeDatabaseDescription": "ბაზს შეკუშვა და ადგილის გათავისუფლება. ამ ამოცანის ბიბლიოთეკის სკანირების ან ნებისმიერი ცვლილების, რომელიც ბაზაში რამეს აკეთებს, გაშვებას შეუძლია ბაზის წარმადობა გაზარდოს." } -- cgit v1.2.3 From 8de052646ca0a852dcad03069a1904433f909653 Mon Sep 17 00:00:00 2001 From: guineu Date: Tue, 27 Dec 2022 14:49:16 +0000 Subject: Translated using Weblate (Catalan) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ca/ --- Emby.Server.Implementations/Localization/Core/ca.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json index c0ed01fdff..50c574d674 100644 --- a/Emby.Server.Implementations/Localization/Core/ca.json +++ b/Emby.Server.Implementations/Localization/Core/ca.json @@ -118,7 +118,7 @@ "TaskCleanActivityLog": "Buidar Registre d'Activitat", "Undefined": "Indefinit", "Forced": "Forçat", - "Default": "Defecte", + "Default": "Per defecte", "TaskOptimizeDatabaseDescription": "Compacta la base de dades i trunca l'espai lliure. Executar aquesta tasca després d’escanejar la biblioteca o fer altres canvis que impliquin modificacions a la base de dades pot millorar el rendiment.", "TaskOptimizeDatabase": "Optimitzar la base de dades", "TaskKeyframeExtractorDescription": "Extreu fotogrames clau dels fitxers de vídeo per crear llistes de reproducció HLS més precises. Aquesta tasca pot durar molt de temps.", -- cgit v1.2.3 From cfda11d5f987de8bfc4e3f2cf47548977f86cf77 Mon Sep 17 00:00:00 2001 From: Xavier Rosell Date: Fri, 30 Dec 2022 10:27:14 +0000 Subject: Translated using Weblate (Catalan) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ca/ --- Emby.Server.Implementations/Localization/Core/ca.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json index 50c574d674..1966f69683 100644 --- a/Emby.Server.Implementations/Localization/Core/ca.json +++ b/Emby.Server.Implementations/Localization/Core/ca.json @@ -40,7 +40,7 @@ "Movies": "Pel·lícules", "Music": "Música", "MusicVideos": "Vídeos Musicals", - "NameInstallFailed": "Instal·lació de {0} fallida", + "NameInstallFailed": "{0} instal·lació fallida", "NameSeasonNumber": "Temporada {0}", "NameSeasonUnknown": "Temporada Desconeguda", "NewVersionIsAvailable": "Una nova versió del Servidor Jellyfin està disponible per descarregar.", -- cgit v1.2.3 From a07b14057ff40675e2b8bba54e17dc9cb710ed64 Mon Sep 17 00:00:00 2001 From: Futoshi Iwashita Date: Wed, 4 Jan 2023 12:55:38 +0000 Subject: Translated using Weblate (Japanese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ja/ --- Emby.Server.Implementations/Localization/Core/ja.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json index d90d705b2e..7f616c35ad 100644 --- a/Emby.Server.Implementations/Localization/Core/ja.json +++ b/Emby.Server.Implementations/Localization/Core/ja.json @@ -122,5 +122,6 @@ "TaskOptimizeDatabase": "データベースの最適化", "TaskKeyframeExtractorDescription": "より正確なHLSプレイリストを作成するため、動画ファイルからキーフレームを抽出する。この処理には時間がかかる場合があります。", "TaskKeyframeExtractor": "キーフレーム抽出", - "External": "外部" + "External": "外部", + "HearingImpaired": "聴覚障害の方" } -- cgit v1.2.3 From c795f17fa6ae9e4f68f23cb569a1129de8fd7635 Mon Sep 17 00:00:00 2001 From: Porrumentzio Date: Tue, 3 Jan 2023 21:19:08 +0000 Subject: Translated using Weblate (Basque) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/eu/ --- Emby.Server.Implementations/Localization/Core/eu.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/eu.json b/Emby.Server.Implementations/Localization/Core/eu.json index d657ac7b69..e91084f920 100644 --- a/Emby.Server.Implementations/Localization/Core/eu.json +++ b/Emby.Server.Implementations/Localization/Core/eu.json @@ -100,7 +100,7 @@ "ItemRemovedWithName": "{0} liburutegitik ezabatu da", "ItemAddedWithName": "{0} liburutegira gehitu da", "HomeVideos": "Etxeko bideoak", - "HeaderNextUp": "Hurrengoa", + "HeaderNextUp": "Nobedadeak", "HeaderLiveTV": "Zuzeneko TB", "HeaderFavoriteSongs": "Gogoko abestiak", "HeaderFavoriteShows": "Gogoko showak", -- cgit v1.2.3 From 769c48c629bae859ba713d66b1a55f35aca708b6 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Sat, 7 Jan 2023 19:30:55 +0100 Subject: Deduplicate media stream ordering code (#9014) --- .../Library/MediaStreamSelector.cs | 54 ++++-------------- jellyfin.ruleset | 2 + .../Library/MediaStreamSelectorTests.cs | 64 ++++++++++++++++++++++ 3 files changed, 78 insertions(+), 42 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index 74c53b2dac..6aef87c525 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -89,17 +89,7 @@ namespace Emby.Server.Implementations.Library // Give some preference to external text subs for better performance return streams .Where(i => i.Type == type) - .OrderBy(i => - { - var index = languagePreferences.FindIndex(x => string.Equals(x, i.Language, StringComparison.OrdinalIgnoreCase)); - - return index == -1 ? 100 : index; - }) - .ThenBy(i => GetBooleanOrderBy(i.IsDefault)) - .ThenBy(i => GetBooleanOrderBy(i.SupportsExternalStream)) - .ThenBy(i => GetBooleanOrderBy(i.IsTextSubtitleStream)) - .ThenBy(i => GetBooleanOrderBy(i.IsExternal)) - .ThenBy(i => i.Index); + .OrderByDescending(i => GetStreamScore(i, languagePreferences)); } public static void SetSubtitleStreamScores( @@ -113,9 +103,9 @@ namespace Emby.Server.Implementations.Library return; } - var sortedStreams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages); + var sortedStreams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages).ToList(); - var filteredStreams = new List(); + List? filteredStreams = null; if (mode == SubtitlePlaybackMode.Default) { @@ -144,46 +134,26 @@ namespace Emby.Server.Implementations.Library } // load forced subs if we have found no suitable full subtitles - var iterStreams = filteredStreams.Count == 0 + var iterStreams = filteredStreams is null || filteredStreams.Count == 0 ? sortedStreams.Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) : filteredStreams; foreach (var stream in iterStreams) { - stream.Score = GetSubtitleScore(stream, preferredLanguages); + stream.Score = GetStreamScore(stream, preferredLanguages); } } - private static int GetSubtitleScore(MediaStream stream, IReadOnlyList languagePreferences) + internal static int GetStreamScore(MediaStream stream, IReadOnlyList languagePreferences) { - var values = new List(); - var index = languagePreferences.FindIndex(x => string.Equals(x, stream.Language, StringComparison.OrdinalIgnoreCase)); - - values.Add(index == -1 ? 0 : 100 - index); - - values.Add(stream.IsForced ? 1 : 0); - values.Add(stream.IsDefault ? 1 : 0); - values.Add(stream.SupportsExternalStream ? 1 : 0); - values.Add(stream.IsTextSubtitleStream ? 1 : 0); - values.Add(stream.IsExternal ? 1 : 0); - - values.Reverse(); - var scale = 1; - var score = 0; - - foreach (var value in values) - { - score += scale * (value + 1); - scale *= 10; - } - + var score = index == -1 ? 1 : 101 - index; + score = (score * 10) + (stream.IsForced ? 2 : 1); + score = (score * 10) + (stream.IsDefault ? 2 : 1); + score = (score * 10) + (stream.SupportsExternalStream ? 2 : 1); + score = (score * 10) + (stream.IsTextSubtitleStream ? 2 : 1); + score = (score * 10) + (stream.IsExternal ? 2 : 1); return score; } - - private static int GetBooleanOrderBy(bool value) - { - return value ? 0 : 1; - } } } diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 3fda774b8d..b611caa11a 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -93,6 +93,8 @@ + + diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/MediaStreamSelectorTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/MediaStreamSelectorTests.cs index 538010f6c0..07feae587b 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/MediaStreamSelectorTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/MediaStreamSelectorTests.cs @@ -51,4 +51,68 @@ public class MediaStreamSelectorTests Assert.Equal(expectedIndex, MediaStreamSelector.GetDefaultAudioStreamIndex(streams, preferredLanguages, preferDefaultTrack)); } + + public static TheoryData GetStreamScore_MediaStream_TestData() + { + var data = new TheoryData(); + + data.Add(new MediaStream(), 111111); + data.Add( + new MediaStream() + { + Language = "eng" + }, + 10111111); + data.Add( + new MediaStream() + { + Language = "fre" + }, + 10011111); + data.Add( + new MediaStream() + { + IsForced = true + }, + 121111); + data.Add( + new MediaStream() + { + IsDefault = true + }, + 112111); + data.Add( + new MediaStream() + { + SupportsExternalStream = true + }, + 111211); + data.Add( + new MediaStream() + { + IsExternal = true + }, + 111112); + data.Add( + new MediaStream() + { + Language = "eng", + IsForced = true, + IsDefault = true, + SupportsExternalStream = true, + IsExternal = true + }, + 10122212); + + return data; + } + + [Theory] + [MemberData(nameof(GetStreamScore_MediaStream_TestData))] + public void GetStreamScore_MediaStream_CorrectScore(MediaStream stream, int expectedScore) + { + var languagePref = new[] { "eng", "fre" }; + + Assert.Equal(expectedScore, MediaStreamSelector.GetStreamScore(stream, languagePref)); + } } -- cgit v1.2.3 From 678bcf9a80ef1fcf7928df6e94030028b4d6c1af Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sat, 7 Jan 2023 11:31:10 -0700 Subject: Use EventManager for AuthenticationSuccess, AuthenticationFailure (#8960) --- Emby.Server.Implementations/Session/SessionManager.cs | 11 ++--------- Jellyfin.Server.Implementations/Users/UserManager.cs | 4 +++- MediaBrowser.Controller/Session/ISessionManager.cs | 10 ---------- 3 files changed, 5 insertions(+), 20 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 2f60d01a92..afa3721b88 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -95,12 +95,6 @@ namespace Emby.Server.Implementations.Session _deviceManager.DeviceOptionsUpdated += OnDeviceManagerDeviceOptionsUpdated; } - /// - public event EventHandler> AuthenticationFailed; - - /// - public event EventHandler> AuthenticationSucceeded; - /// /// Occurs when playback has started. /// @@ -1468,7 +1462,7 @@ namespace Emby.Server.Implementations.Session if (user is null) { - AuthenticationFailed?.Invoke(this, new GenericEventArgs(request)); + await _eventManager.PublishAsync(new GenericEventArgs(request)).ConfigureAwait(false); throw new AuthenticationException("Invalid username or password entered."); } @@ -1504,8 +1498,7 @@ namespace Emby.Server.Implementations.Session ServerId = _appHost.SystemId }; - AuthenticationSucceeded?.Invoke(this, new GenericEventArgs(returnResult)); - + await _eventManager.PublishAsync(new GenericEventArgs(returnResult)).ConfigureAwait(false); return returnResult; } diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index ae3fcad29b..19ac007b93 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -157,7 +157,9 @@ namespace Jellyfin.Server.Implementations.Users await UpdateUserInternalAsync(dbContext, user).ConfigureAwait(false); } - OnUserUpdated?.Invoke(this, new GenericEventArgs(user)); + var eventArgs = new UserUpdatedEventArgs(user); + await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false); + OnUserUpdated?.Invoke(this, eventArgs); } /// diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index b16399598c..eefc5d222e 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -57,16 +57,6 @@ namespace MediaBrowser.Controller.Session /// event EventHandler CapabilitiesChanged; - /// - /// Occurs when [authentication failed]. - /// - event EventHandler> AuthenticationFailed; - - /// - /// Occurs when [authentication succeeded]. - /// - event EventHandler> AuthenticationSucceeded; - /// /// Gets the sessions. /// -- cgit v1.2.3 From ac9b7142cc0b479c9123b149521c1cad969623a1 Mon Sep 17 00:00:00 2001 From: Brad Beattie Date: Sat, 7 Jan 2023 10:31:33 -0800 Subject: Fixing similar parental rating calculation (#8959) --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 9bdc4e5c8e..763ff77f10 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -2401,13 +2401,17 @@ namespace Emby.Server.Implementations.Data var builder = new StringBuilder(); builder.Append('('); - if (string.IsNullOrEmpty(item.OfficialRating)) + if (item.InheritedParentalRatingValue == 0) { - builder.Append("(OfficialRating is null * 10)"); + builder.Append("((InheritedParentalRatingValue=0) * 10)"); } else { - builder.Append("(OfficialRating=@ItemOfficialRating * 10)"); + builder.Append( + @"(SELECT CASE WHEN InheritedParentalRatingValue=0 + THEN 0 + ELSE 10.0 / (1.0 + ABS(InheritedParentalRatingValue - @InheritedParentalRatingValue)) + END)"); } if (item.ProductionYear.HasValue) @@ -2521,6 +2525,11 @@ namespace Emby.Server.Implementations.Data { statement.TryBind("@SimilarItemId", item.Id); } + + if (commandText.Contains("@InheritedParentalRatingValue", StringComparison.OrdinalIgnoreCase)) + { + statement.TryBind("@InheritedParentalRatingValue", item.InheritedParentalRatingValue); + } } private string GetJoinUserDataText(InternalItemsQuery query) -- cgit v1.2.3 From 34fe2d00a2ccca462666b56f677922e4922d8ed7 Mon Sep 17 00:00:00 2001 From: Retrial Date: Fri, 6 Jan 2023 19:09:28 +0000 Subject: Translated using Weblate (Greek) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/el/ --- .../Localization/Core/el.json | 42 +++++++++++----------- 1 file changed, 21 insertions(+), 21 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json index 8e9287af48..c6e2244cae 100644 --- a/Emby.Server.Implementations/Localization/Core/el.json +++ b/Emby.Server.Implementations/Localization/Core/el.json @@ -15,7 +15,7 @@ "Favorites": "Αγαπημένα", "Folders": "Φάκελοι", "Genres": "Είδη", - "HeaderAlbumArtists": "Δισκογραφικοί καλλιτέχνες", + "HeaderAlbumArtists": "Καλλιτέχνες άλμπουμ", "HeaderContinueWatching": "Συνεχίστε την παρακολούθηση", "HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ", "HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες", @@ -24,8 +24,8 @@ "HeaderFavoriteSongs": "Αγαπημένα Τραγούδια", "HeaderLiveTV": "Ζωντανή Τηλεόραση", "HeaderNextUp": "Επόμενο", - "HeaderRecordingGroups": "Μουσικά Συγκροτήματα", - "HomeVideos": "Προσωπικά βίντεο", + "HeaderRecordingGroups": "Ομάδες Ηχογράφησης", + "HomeVideos": "Προσωπικά Βίντεο", "Inherit": "Κληρονόμηση", "ItemAddedWithName": "{0} προστέθηκε στη βιβλιοθήκη", "ItemRemovedWithName": "{0} διαγράφηκε από τη βιβλιοθήκη", @@ -51,10 +51,10 @@ "NotificationOptionCameraImageUploaded": "Μεταφορτώθηκε φωτογραφία απο κάμερα", "NotificationOptionInstallationFailed": "Αποτυχία εγκατάστασης", "NotificationOptionNewLibraryContent": "Προστέθηκε νέο περιεχόμενο", - "NotificationOptionPluginError": "Αποτυχία του plugin", - "NotificationOptionPluginInstalled": "Το plugin εγκαταστάθηκε", - "NotificationOptionPluginUninstalled": "Το plugin απεγκαταστάθηκε", - "NotificationOptionPluginUpdateInstalled": "Η αναβάθμιση του plugin εγκαταστάθηκε", + "NotificationOptionPluginError": "Αποτυχία του πρόσθετου", + "NotificationOptionPluginInstalled": "Το πρόσθετο εγκαταστάθηκε", + "NotificationOptionPluginUninstalled": "Το πρόσθετο απεγκαταστάθηκε", + "NotificationOptionPluginUpdateInstalled": "Η αναβάθμιση του πρόσθετου εγκαταστάθηκε", "NotificationOptionServerRestartRequired": "Ο διακομιστής χρειάζεται επανεκκίνηση", "NotificationOptionTaskFailed": "Αποτυχία προγραμματισμένης εργασίας", "NotificationOptionUserLockedOut": "Ο χρήστης αποκλείστηκε", @@ -66,7 +66,7 @@ "PluginInstalledWithName": "{0} εγκαταστήθηκε", "PluginUninstalledWithName": "{0} έχει απεγκατασταθεί", "PluginUpdatedWithName": "{0} έχει αναβαθμιστεί", - "ProviderValue": "Provider: {0}", + "ProviderValue": "Πάροχος: {0}", "ScheduledTaskFailedWithName": "{0} αποτυχία", "ScheduledTaskStartedWithName": "{0} ξεκίνησε", "ServerNameNeedsToBeRestarted": "{0} χρειάζεται επανεκκίνηση", @@ -79,7 +79,7 @@ "System": "Σύστημα", "TvShows": "Τηλεοπτικές Σειρές", "User": "Χρήστης", - "UserCreatedWithName": "Δημιουργήθηκε ο χρήστης {0}", + "UserCreatedWithName": "Ο χρήστης {0} δημιουργήθηκε", "UserDeletedWithName": "Ο χρήστης {0} έχει διαγραφεί", "UserDownloadingItemWithValues": "{0} κατεβάζει {1}", "UserLockedOutWithName": "Ο χρήστης {0} αποκλείστηκε", @@ -93,29 +93,29 @@ "ValueSpecialEpisodeName": "Σπέσιαλ - {0}", "VersionNumber": "Έκδοση {0}", "TaskRefreshPeople": "Ανανέωση Ατόμων", - "TaskCleanLogsDescription": "Διαγράφει τα αρχεία καταγραφής που είναι άνω των {0} ημερών.", - "TaskCleanLogs": "Καθαρισμός Καταλόγου Καταγραφής", - "TaskRefreshLibraryDescription": "Σαρώνει την βιβλιοθήκη πολυμέσων σας για νέα αρχεία και αναζωογονεί τα μεταδεδομένα.", + "TaskCleanLogsDescription": "Διαγράφει αρχεία καταγραφής που είναι πάνω από {0} ημέρες.", + "TaskCleanLogs": "Εκκαθάριση Καταλόγου Καταγραφής", + "TaskRefreshLibraryDescription": "Σαρώνει την βιβλιοθήκη πολυμέσων σας για νέα αρχεία και ανανεώνει τα μεταδεδομένα.", "TaskRefreshLibrary": "Βιβλιοθήκη Σάρωσης Πολυμέσων", - "TaskRefreshChapterImagesDescription": "Δημιουργεί μικρογραφίες για βίντεο με κεφάλαια.", + "TaskRefreshChapterImagesDescription": "Δημιουργεί μικρογραφίες για βίντεο που έχουν κεφάλαια.", "TaskRefreshChapterImages": "Εξαγωγή Εικόνων Κεφαλαίου", - "TaskCleanCacheDescription": "Τα διαγραμμένα αρχεία προσωρινής μνήμης που δεν χρειάζονται πλέον από το σύστημα.", + "TaskCleanCacheDescription": "Διαγράφει αρχεία προσωρινής μνήμης που δεν χρειάζονται πλέον το σύστημα.", "TaskCleanCache": "Καθαρισμός Καταλόγου Προσωρινής Μνήμης", "TasksChannelsCategory": "Κανάλια Διαδικτύου", "TasksApplicationCategory": "Εφαρμογή", "TasksLibraryCategory": "Βιβλιοθήκη", "TasksMaintenanceCategory": "Συντήρηση", - "TaskDownloadMissingSubtitlesDescription": "Αναζητήσεις στο διαδίκτυο όπου λείπουν υπότιτλους με βάση τη διαμόρφωση μεταδεδομένων.", + "TaskDownloadMissingSubtitlesDescription": "Ψάχνει στο διαδίκτυο για υπότιτλους που λείπουν με βάση τη διαμόρφωση μεταδεδομένων.", "TaskDownloadMissingSubtitles": "Λήψη υπότιτλων που λείπουν", "TaskRefreshChannelsDescription": "Ανανεώνει τις πληροφορίες καναλιού στο διαδικτύου.", "TaskRefreshChannels": "Ανανέωση Καναλιών", - "TaskCleanTranscodeDescription": "Διαγράφει αρχείου διακωδικοποιητή περισσότερο από μία ημέρα.", - "TaskCleanTranscode": "Καθαρισμός Kαταλόγου Διακωδικοποιητή", - "TaskUpdatePluginsDescription": "Κατεβάζει και εγκαθιστά ενημερώσεις για τις προσθήκες που έχουν ρυθμιστεί για αυτόματη ενημέρωση.", - "TaskUpdatePlugins": "Ενημέρωση Προσθηκών", - "TaskRefreshPeopleDescription": "Ενημερώνει μεταδεδομένα για ηθοποιούς και σκηνοθέτες στην βιβλιοθήκη των πολυμέσων σας.", + "TaskCleanTranscodeDescription": "Διαγράφει αρχεία διακωδικοποίησης άνω της μίας ημέρας.", + "TaskCleanTranscode": "Εκκαθάριση Kαταλόγου Διακωδικοποίησης", + "TaskUpdatePluginsDescription": "Κατεβάζει και εγκαθιστά ενημερώσεις για τα πρόσθετα που έχουν ρυθμιστεί για αυτόματη ενημέρωση.", + "TaskUpdatePlugins": "Ενημέρωση Πρόσθετων", + "TaskRefreshPeopleDescription": "Ενημερώνει τα μεταδεδομένα για ηθοποιούς και σκηνοθέτες στη βιβλιοθήκη πολυμέσων σας.", "TaskCleanActivityLogDescription": "Διαγράφει καταχωρήσεις απο το αρχείο καταγραφής παλαιότερες από την επιλεγμένη ηλικία.", - "TaskCleanActivityLog": "Καθαρό Αρχείο Καταγραφής Δραστηριοτήτων", + "TaskCleanActivityLog": "Εκκαθάριση Αρχείου Καταγραφής Δραστηριοτήτων", "Undefined": "Απροσδιόριστο", "Forced": "Εξαναγκασμένο", "Default": "Προεπιλογή", -- cgit v1.2.3 From 51c7c85b4cdaed9e86fc8699de61e489c9223a17 Mon Sep 17 00:00:00 2001 From: Stephen Cox Date: Fri, 6 Jan 2023 21:54:37 +0000 Subject: Translated using Weblate (Afrikaans) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/af/ --- Emby.Server.Implementations/Localization/Core/af.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json index f356c98a93..9fbf364efe 100644 --- a/Emby.Server.Implementations/Localization/Core/af.json +++ b/Emby.Server.Implementations/Localization/Core/af.json @@ -122,5 +122,6 @@ "TaskOptimizeDatabase": "Optimaliseer databasis", "TaskKeyframeExtractorDescription": "Haal keyframes vanuit video lêers om meer presiese HLS afspeellyste te maak. Dit kan lank duur.", "TaskKeyframeExtractor": "Keyframe Ekstraktor", - "External": "Ekstern" + "External": "Ekstern", + "HearingImpaired": "gehoorgestremd" } -- cgit v1.2.3 From 8af854315ef2ad78d37c25fd3def4efe6392a3ca Mon Sep 17 00:00:00 2001 From: MBR-0001 <55142207+MBR-0001@users.noreply.github.com> Date: Mon, 9 Jan 2023 20:47:12 +0100 Subject: Add Chinese Bilingual language (#7623) Closes https://github.com/jellyfin/jellyfin-plugin-opensubtitles/issues/103 --- Emby.Server.Implementations/Localization/iso6392.txt | 1 + .../Localization/LocalizationManagerTests.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/iso6392.txt b/Emby.Server.Implementations/Localization/iso6392.txt index 66fba33304..b55c0fa330 100644 --- a/Emby.Server.Implementations/Localization/iso6392.txt +++ b/Emby.Server.Implementations/Localization/iso6392.txt @@ -77,6 +77,7 @@ chb|||Chibcha|chibcha che||ce|Chechen|tchétchène chg|||Chagatai|djaghataï chi|zho|zh|Chinese|chinois +chi|zho|ze|Chinese; Bilingual|chinois chi|zho|zh-tw|Chinese; Traditional|chinois chi|zho|zh-hk|Chinese; Hong Kong|chinois chk|||Chuukese|chuuk diff --git a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs index 3e7d6ed1dc..16eb7a75c6 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs @@ -40,7 +40,7 @@ namespace Jellyfin.Server.Implementations.Tests.Localization await localizationManager.LoadAll(); var cultures = localizationManager.GetCultures().ToList(); - Assert.Equal(190, cultures.Count); + Assert.Equal(191, cultures.Count); var germany = cultures.FirstOrDefault(x => x.TwoLetterISOLanguageName.Equals("de", StringComparison.Ordinal)); Assert.NotNull(germany); -- cgit v1.2.3 From b5da0d1b1775bbbf7acb29a5ebebca7ccd9e8f2e Mon Sep 17 00:00:00 2001 From: Patrick Barron <18354464+barronpm@users.noreply.github.com> Date: Tue, 10 Jan 2023 07:51:46 -0500 Subject: Rename Emby.Drawing and move to src (#9054) * Move Emby.Drawing to src * Rename Emby.Drawing -> Jellyfin.Drawing --- Emby.Drawing/Emby.Drawing.csproj | 35 -- Emby.Drawing/ImageProcessor.cs | 569 --------------------- Emby.Drawing/NullImageEncoder.cs | 58 --- Emby.Drawing/Properties/AssemblyInfo.cs | 30 -- Emby.Server.Implementations/ApplicationHost.cs | 2 +- .../Emby.Server.Implementations.csproj | 2 +- Jellyfin.Server/CoreAppHost.cs | 2 +- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- Jellyfin.sln | 3 +- src/Jellyfin.Drawing/ImageProcessor.cs | 569 +++++++++++++++++++++ src/Jellyfin.Drawing/Jellyfin.Drawing.csproj | 35 ++ src/Jellyfin.Drawing/NullImageEncoder.cs | 58 +++ src/Jellyfin.Drawing/Properties/AssemblyInfo.cs | 30 ++ 13 files changed, 698 insertions(+), 697 deletions(-) delete mode 100644 Emby.Drawing/Emby.Drawing.csproj delete mode 100644 Emby.Drawing/ImageProcessor.cs delete mode 100644 Emby.Drawing/NullImageEncoder.cs delete mode 100644 Emby.Drawing/Properties/AssemblyInfo.cs create mode 100644 src/Jellyfin.Drawing/ImageProcessor.cs create mode 100644 src/Jellyfin.Drawing/Jellyfin.Drawing.csproj create mode 100644 src/Jellyfin.Drawing/NullImageEncoder.cs create mode 100644 src/Jellyfin.Drawing/Properties/AssemblyInfo.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj deleted file mode 100644 index 5bf226408b..0000000000 --- a/Emby.Drawing/Emby.Drawing.csproj +++ /dev/null @@ -1,35 +0,0 @@ - - - - - {08FFF49B-F175-4807-A2B5-73B0EBD9F716} - - - - net7.0 - false - true - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers - - - - - - - diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs deleted file mode 100644 index 5a49e876a9..0000000000 --- a/Emby.Drawing/ImageProcessor.cs +++ /dev/null @@ -1,569 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net.Mime; -using System.Text; -using System.Threading.Tasks; -using Jellyfin.Data.Entities; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; -using Microsoft.Extensions.Logging; -using Photo = MediaBrowser.Controller.Entities.Photo; - -namespace Emby.Drawing -{ - /// - /// Class ImageProcessor. - /// - public sealed class ImageProcessor : IImageProcessor, IDisposable - { - // Increment this when there's a change requiring caches to be invalidated - private const char Version = '3'; - - private static readonly HashSet _transparentImageTypes - = new HashSet(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" }; - - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - private readonly IServerApplicationPaths _appPaths; - private readonly IImageEncoder _imageEncoder; - private readonly IMediaEncoder _mediaEncoder; - - private bool _disposed; - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - /// The server application paths. - /// The filesystem. - /// The image encoder. - /// The media encoder. - public ImageProcessor( - ILogger logger, - IServerApplicationPaths appPaths, - IFileSystem fileSystem, - IImageEncoder imageEncoder, - IMediaEncoder mediaEncoder) - { - _logger = logger; - _fileSystem = fileSystem; - _imageEncoder = imageEncoder; - _mediaEncoder = mediaEncoder; - _appPaths = appPaths; - } - - private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images"); - - /// - public IReadOnlyCollection SupportedInputFormats => - new HashSet(StringComparer.OrdinalIgnoreCase) - { - "tiff", - "tif", - "jpeg", - "jpg", - "png", - "aiff", - "cr2", - "crw", - "nef", - "orf", - "pef", - "arw", - "webp", - "gif", - "bmp", - "erf", - "raf", - "rw2", - "nrw", - "dng", - "ico", - "astc", - "ktx", - "pkm", - "wbmp" - }; - - /// - public bool SupportsImageCollageCreation => _imageEncoder.SupportsImageCollageCreation; - - /// - public async Task ProcessImage(ImageProcessingOptions options, Stream toStream) - { - var file = await ProcessImage(options).ConfigureAwait(false); - using (var fileStream = AsyncFile.OpenRead(file.Path)) - { - await fileStream.CopyToAsync(toStream).ConfigureAwait(false); - } - } - - /// - public IReadOnlyCollection GetSupportedImageOutputFormats() - => _imageEncoder.SupportedOutputFormats; - - /// - public bool SupportsTransparency(string path) - => _transparentImageTypes.Contains(Path.GetExtension(path)); - - /// - public async Task<(string Path, string? MimeType, DateTime DateModified)> ProcessImage(ImageProcessingOptions options) - { - ItemImageInfo originalImage = options.Image; - BaseItem item = options.Item; - - string originalImagePath = originalImage.Path; - DateTime dateModified = originalImage.DateModified; - ImageDimensions? originalImageSize = null; - if (originalImage.Width > 0 && originalImage.Height > 0) - { - originalImageSize = new ImageDimensions(originalImage.Width, originalImage.Height); - } - - var mimeType = MimeTypes.GetMimeType(originalImagePath); - if (!_imageEncoder.SupportsImageEncoding) - { - return (originalImagePath, mimeType, dateModified); - } - - var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false); - originalImagePath = supportedImageInfo.Path; - - // Original file doesn't exist, or original file is gif. - if (!File.Exists(originalImagePath) || string.Equals(mimeType, MediaTypeNames.Image.Gif, StringComparison.OrdinalIgnoreCase)) - { - return (originalImagePath, mimeType, dateModified); - } - - dateModified = supportedImageInfo.DateModified; - bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath)); - - bool autoOrient = false; - ImageOrientation? orientation = null; - if (item is Photo photo) - { - if (photo.Orientation.HasValue) - { - if (photo.Orientation.Value != ImageOrientation.TopLeft) - { - autoOrient = true; - orientation = photo.Orientation; - } - } - else - { - // Orientation unknown, so do it - autoOrient = true; - orientation = photo.Orientation; - } - } - - if (options.HasDefaultOptions(originalImagePath, originalImageSize) && (!autoOrient || !options.RequiresAutoOrientation)) - { - // Just spit out the original file if all the options are default - return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); - } - - int quality = options.Quality; - - ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency); - string cacheFilePath = GetCacheFilePath( - originalImagePath, - options.Width, - options.Height, - options.MaxWidth, - options.MaxHeight, - options.FillWidth, - options.FillHeight, - quality, - dateModified, - outputFormat, - options.AddPlayedIndicator, - options.PercentPlayed, - options.UnplayedCount, - options.Blur, - options.BackgroundColor, - options.ForegroundLayer); - - try - { - if (!File.Exists(cacheFilePath)) - { - string resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, cacheFilePath, autoOrient, orientation, quality, options, outputFormat); - - if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase)) - { - return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); - } - } - - return (cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath)); - } - catch (Exception ex) - { - // If it fails for whatever reason, return the original image - _logger.LogError(ex, "Error encoding image"); - return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); - } - } - - private ImageFormat GetOutputFormat(IReadOnlyCollection clientSupportedFormats, bool requiresTransparency) - { - var serverFormats = GetSupportedImageOutputFormats(); - - // Client doesn't care about format, so start with webp if supported - if (serverFormats.Contains(ImageFormat.Webp) && clientSupportedFormats.Contains(ImageFormat.Webp)) - { - return ImageFormat.Webp; - } - - // If transparency is needed and webp isn't supported, than png is the only option - if (requiresTransparency && clientSupportedFormats.Contains(ImageFormat.Png)) - { - return ImageFormat.Png; - } - - foreach (var format in clientSupportedFormats) - { - if (serverFormats.Contains(format)) - { - return format; - } - } - - // We should never actually get here - return ImageFormat.Jpg; - } - - private string GetMimeType(ImageFormat format, string path) - => format switch - { - ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"), - ImageFormat.Gif => MimeTypes.GetMimeType("i.gif"), - ImageFormat.Jpg => MimeTypes.GetMimeType("i.jpg"), - ImageFormat.Png => MimeTypes.GetMimeType("i.png"), - ImageFormat.Webp => MimeTypes.GetMimeType("i.webp"), - _ => MimeTypes.GetMimeType(path) - }; - - /// - /// Gets the cache file path based on a set of parameters. - /// - private string GetCacheFilePath( - string originalPath, - int? width, - int? height, - int? maxWidth, - int? maxHeight, - int? fillWidth, - int? fillHeight, - int quality, - DateTime dateModified, - ImageFormat format, - bool addPlayedIndicator, - double percentPlayed, - int? unwatchedCount, - int? blur, - string backgroundColor, - string foregroundLayer) - { - var filename = new StringBuilder(256); - filename.Append(originalPath); - - filename.Append(",quality="); - filename.Append(quality); - - filename.Append(",datemodified="); - filename.Append(dateModified.Ticks); - - filename.Append(",f="); - filename.Append(format); - - if (width.HasValue) - { - filename.Append(",width="); - filename.Append(width.Value); - } - - if (height.HasValue) - { - filename.Append(",height="); - filename.Append(height.Value); - } - - if (maxWidth.HasValue) - { - filename.Append(",maxwidth="); - filename.Append(maxWidth.Value); - } - - if (maxHeight.HasValue) - { - filename.Append(",maxheight="); - filename.Append(maxHeight.Value); - } - - if (fillWidth.HasValue) - { - filename.Append(",fillwidth="); - filename.Append(fillWidth.Value); - } - - if (fillHeight.HasValue) - { - filename.Append(",fillheight="); - filename.Append(fillHeight.Value); - } - - if (addPlayedIndicator) - { - filename.Append(",pl=true"); - } - - if (percentPlayed > 0) - { - filename.Append(",p="); - filename.Append(percentPlayed); - } - - if (unwatchedCount.HasValue) - { - filename.Append(",p="); - filename.Append(unwatchedCount.Value); - } - - if (blur.HasValue) - { - filename.Append(",blur="); - filename.Append(blur.Value); - } - - if (!string.IsNullOrEmpty(backgroundColor)) - { - filename.Append(",b="); - filename.Append(backgroundColor); - } - - if (!string.IsNullOrEmpty(foregroundLayer)) - { - filename.Append(",fl="); - filename.Append(foregroundLayer); - } - - filename.Append(",v="); - filename.Append(Version); - - return GetCachePath(ResizedImageCachePath, filename.ToString(), "." + format.ToString().ToLowerInvariant()); - } - - /// - public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info) - { - int width = info.Width; - int height = info.Height; - - if (height > 0 && width > 0) - { - return new ImageDimensions(width, height); - } - - string path = info.Path; - _logger.LogDebug("Getting image size for item {ItemType} {Path}", item.GetType().Name, path); - - ImageDimensions size = GetImageDimensions(path); - info.Width = size.Width; - info.Height = size.Height; - - return size; - } - - /// - public ImageDimensions GetImageDimensions(string path) - => _imageEncoder.GetImageSize(path); - - /// - public string GetImageBlurHash(string path) - { - var size = GetImageDimensions(path); - return GetImageBlurHash(path, size); - } - - /// - public string GetImageBlurHash(string path, ImageDimensions imageDimensions) - { - if (imageDimensions.Width <= 0 || imageDimensions.Height <= 0) - { - return string.Empty; - } - - // We want tiles to be as close to square as possible, and to *mostly* keep under 16 tiles for performance. - // One tile is (width / xComp) x (height / yComp) pixels, which means that ideally yComp = xComp * height / width. - // See more at https://github.com/woltapp/blurhash/#how-do-i-pick-the-number-of-x-and-y-components - float xCompF = MathF.Sqrt(16.0f * imageDimensions.Width / imageDimensions.Height); - float yCompF = xCompF * imageDimensions.Height / imageDimensions.Width; - - int xComp = Math.Min((int)xCompF + 1, 9); - int yComp = Math.Min((int)yCompF + 1, 9); - - return _imageEncoder.GetImageBlurHash(xComp, yComp, path); - } - - /// - public string GetImageCacheTag(BaseItem item, ItemImageInfo image) - => (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture); - - /// - public string GetImageCacheTag(BaseItem item, ChapterInfo chapter) - { - return GetImageCacheTag(item, new ItemImageInfo - { - Path = chapter.ImagePath, - Type = ImageType.Chapter, - DateModified = chapter.ImageDateModified - }); - } - - /// - public string? GetImageCacheTag(User user) - { - if (user.ProfileImage is null) - { - return null; - } - - return (user.ProfileImage.Path + user.ProfileImage.LastModified.Ticks).GetMD5() - .ToString("N", CultureInfo.InvariantCulture); - } - - private Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified) - { - var inputFormat = Path.GetExtension(originalImagePath.AsSpan()).TrimStart('.').ToString(); - - // These are just jpg files renamed as tbn - if (string.Equals(inputFormat, "tbn", StringComparison.OrdinalIgnoreCase)) - { - return Task.FromResult((originalImagePath, dateModified)); - } - - // TODO _mediaEncoder.ConvertImage is not implemented - // if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat)) - // { - // try - // { - // string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture); - // - // string cacheExtension = _mediaEncoder.SupportsEncoder("libwebp") ? ".webp" : ".png"; - // var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension); - // - // var file = _fileSystem.GetFileInfo(outputPath); - // if (!file.Exists) - // { - // await _mediaEncoder.ConvertImage(originalImagePath, outputPath).ConfigureAwait(false); - // dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath); - // } - // else - // { - // dateModified = file.LastWriteTimeUtc; - // } - // - // originalImagePath = outputPath; - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "Image conversion failed for {Path}", originalImagePath); - // } - // } - - return Task.FromResult((originalImagePath, dateModified)); - } - - /// - /// Gets the cache path. - /// - /// The path. - /// Name of the unique. - /// The file extension. - /// System.String. - /// - /// path - /// or - /// uniqueName - /// or - /// fileExtension. - /// - public string GetCachePath(string path, string uniqueName, string fileExtension) - { - ArgumentException.ThrowIfNullOrEmpty(path); - ArgumentException.ThrowIfNullOrEmpty(uniqueName); - ArgumentException.ThrowIfNullOrEmpty(fileExtension); - - var filename = uniqueName.GetMD5() + fileExtension; - - return GetCachePath(path, filename); - } - - /// - /// Gets the cache path. - /// - /// The path. - /// The filename. - /// System.String. - /// - /// path - /// or - /// filename. - /// - public string GetCachePath(ReadOnlySpan path, ReadOnlySpan filename) - { - if (path.IsEmpty) - { - throw new ArgumentException("Path can't be empty.", nameof(path)); - } - - if (filename.IsEmpty) - { - throw new ArgumentException("Filename can't be empty.", nameof(filename)); - } - - var prefix = filename.Slice(0, 1); - - return Path.Join(path, prefix, filename); - } - - /// - public void CreateImageCollage(ImageCollageOptions options, string? libraryName) - { - _logger.LogInformation("Creating image collage and saving to {Path}", options.OutputPath); - - _imageEncoder.CreateImageCollage(options, libraryName); - - _logger.LogInformation("Completed creation of image collage and saved to {Path}", options.OutputPath); - } - - /// - public void Dispose() - { - if (_disposed) - { - return; - } - - if (_imageEncoder is IDisposable disposable) - { - disposable.Dispose(); - } - - _disposed = true; - } - } -} diff --git a/Emby.Drawing/NullImageEncoder.cs b/Emby.Drawing/NullImageEncoder.cs deleted file mode 100644 index d0a26b713b..0000000000 --- a/Emby.Drawing/NullImageEncoder.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Model.Drawing; - -namespace Emby.Drawing -{ - /// - /// A fallback implementation of . - /// - public class NullImageEncoder : IImageEncoder - { - /// - public IReadOnlyCollection SupportedInputFormats - => new HashSet(StringComparer.OrdinalIgnoreCase) { "png", "jpeg", "jpg" }; - - /// - public IReadOnlyCollection SupportedOutputFormats - => new HashSet() { ImageFormat.Jpg, ImageFormat.Png }; - - /// - public string Name => "Null Image Encoder"; - - /// - public bool SupportsImageCollageCreation => false; - - /// - public bool SupportsImageEncoding => false; - - /// - public ImageDimensions GetImageSize(string path) - => throw new NotImplementedException(); - - /// - public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat) - { - throw new NotImplementedException(); - } - - /// - public void CreateImageCollage(ImageCollageOptions options, string? libraryName) - { - throw new NotImplementedException(); - } - - /// - public void CreateSplashscreen(IReadOnlyList posters, IReadOnlyList backdrops) - { - throw new NotImplementedException(); - } - - /// - public string GetImageBlurHash(int xComp, int yComp, string path) - { - throw new NotImplementedException(); - } - } -} diff --git a/Emby.Drawing/Properties/AssemblyInfo.cs b/Emby.Drawing/Properties/AssemblyInfo.cs deleted file mode 100644 index 281008e370..0000000000 --- a/Emby.Drawing/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Emby.Drawing")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("87b6f14e-16d8-4a58-a553-fd9945e47458")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 5db3748bf7..7b3d07dfc1 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -18,7 +18,6 @@ using System.Threading.Tasks; using Emby.Dlna; using Emby.Dlna.Main; using Emby.Dlna.Ssdp; -using Emby.Drawing; using Emby.Naming.Common; using Emby.Notifications; using Emby.Photos; @@ -45,6 +44,7 @@ using Emby.Server.Implementations.SyncPlay; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; using Jellyfin.Api.Helpers; +using Jellyfin.Drawing; using Jellyfin.MediaEncoding.Hls.Playlist; using Jellyfin.Networking.Configuration; using Jellyfin.Networking.Manager; diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index f46affc73b..b18a3174a0 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -18,7 +18,7 @@ - + diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 002193baf5..d70b8f3ab7 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Reflection; -using Emby.Drawing; using Emby.Server.Implementations; using Emby.Server.Implementations.Session; using Jellyfin.Api.WebSocketListeners; +using Jellyfin.Drawing; using Jellyfin.Drawing.Skia; using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Activity; diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index ac20869353..2be628ac22 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -52,7 +52,7 @@ - + diff --git a/Jellyfin.sln b/Jellyfin.sln index 0514b9614c..347716eb9a 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -17,7 +17,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.XbmcMetadata", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.LocalMetadata", "MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj", "{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing", "src\Jellyfin.Drawing\Jellyfin.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Photos", "Emby.Photos\Emby.Photos.csproj", "{89AB4548-770D-41FD-A891-8DAFF44F452C}" EndProject @@ -287,6 +287,7 @@ Global {DA9FD356-4894-4830-B208-D6BCE3E65B11} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C} {FE47334C-EFDE-4519-BD50-F24430FF360B} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {24960660-DE6C-47BF-AEEF-CEE8F19FE6C2} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {08FFF49B-F175-4807-A2B5-73B0EBD9F716} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} diff --git a/src/Jellyfin.Drawing/ImageProcessor.cs b/src/Jellyfin.Drawing/ImageProcessor.cs new file mode 100644 index 0000000000..3c7bc394f0 --- /dev/null +++ b/src/Jellyfin.Drawing/ImageProcessor.cs @@ -0,0 +1,569 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net.Mime; +using System.Text; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; +using Microsoft.Extensions.Logging; +using Photo = MediaBrowser.Controller.Entities.Photo; + +namespace Jellyfin.Drawing +{ + /// + /// Class ImageProcessor. + /// + public sealed class ImageProcessor : IImageProcessor, IDisposable + { + // Increment this when there's a change requiring caches to be invalidated + private const char Version = '3'; + + private static readonly HashSet _transparentImageTypes + = new HashSet(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" }; + + private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; + private readonly IServerApplicationPaths _appPaths; + private readonly IImageEncoder _imageEncoder; + private readonly IMediaEncoder _mediaEncoder; + + private bool _disposed; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The server application paths. + /// The filesystem. + /// The image encoder. + /// The media encoder. + public ImageProcessor( + ILogger logger, + IServerApplicationPaths appPaths, + IFileSystem fileSystem, + IImageEncoder imageEncoder, + IMediaEncoder mediaEncoder) + { + _logger = logger; + _fileSystem = fileSystem; + _imageEncoder = imageEncoder; + _mediaEncoder = mediaEncoder; + _appPaths = appPaths; + } + + private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images"); + + /// + public IReadOnlyCollection SupportedInputFormats => + new HashSet(StringComparer.OrdinalIgnoreCase) + { + "tiff", + "tif", + "jpeg", + "jpg", + "png", + "aiff", + "cr2", + "crw", + "nef", + "orf", + "pef", + "arw", + "webp", + "gif", + "bmp", + "erf", + "raf", + "rw2", + "nrw", + "dng", + "ico", + "astc", + "ktx", + "pkm", + "wbmp" + }; + + /// + public bool SupportsImageCollageCreation => _imageEncoder.SupportsImageCollageCreation; + + /// + public async Task ProcessImage(ImageProcessingOptions options, Stream toStream) + { + var file = await ProcessImage(options).ConfigureAwait(false); + using (var fileStream = AsyncFile.OpenRead(file.Path)) + { + await fileStream.CopyToAsync(toStream).ConfigureAwait(false); + } + } + + /// + public IReadOnlyCollection GetSupportedImageOutputFormats() + => _imageEncoder.SupportedOutputFormats; + + /// + public bool SupportsTransparency(string path) + => _transparentImageTypes.Contains(Path.GetExtension(path)); + + /// + public async Task<(string Path, string? MimeType, DateTime DateModified)> ProcessImage(ImageProcessingOptions options) + { + ItemImageInfo originalImage = options.Image; + BaseItem item = options.Item; + + string originalImagePath = originalImage.Path; + DateTime dateModified = originalImage.DateModified; + ImageDimensions? originalImageSize = null; + if (originalImage.Width > 0 && originalImage.Height > 0) + { + originalImageSize = new ImageDimensions(originalImage.Width, originalImage.Height); + } + + var mimeType = MimeTypes.GetMimeType(originalImagePath); + if (!_imageEncoder.SupportsImageEncoding) + { + return (originalImagePath, mimeType, dateModified); + } + + var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false); + originalImagePath = supportedImageInfo.Path; + + // Original file doesn't exist, or original file is gif. + if (!File.Exists(originalImagePath) || string.Equals(mimeType, MediaTypeNames.Image.Gif, StringComparison.OrdinalIgnoreCase)) + { + return (originalImagePath, mimeType, dateModified); + } + + dateModified = supportedImageInfo.DateModified; + bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath)); + + bool autoOrient = false; + ImageOrientation? orientation = null; + if (item is Photo photo) + { + if (photo.Orientation.HasValue) + { + if (photo.Orientation.Value != ImageOrientation.TopLeft) + { + autoOrient = true; + orientation = photo.Orientation; + } + } + else + { + // Orientation unknown, so do it + autoOrient = true; + orientation = photo.Orientation; + } + } + + if (options.HasDefaultOptions(originalImagePath, originalImageSize) && (!autoOrient || !options.RequiresAutoOrientation)) + { + // Just spit out the original file if all the options are default + return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); + } + + int quality = options.Quality; + + ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency); + string cacheFilePath = GetCacheFilePath( + originalImagePath, + options.Width, + options.Height, + options.MaxWidth, + options.MaxHeight, + options.FillWidth, + options.FillHeight, + quality, + dateModified, + outputFormat, + options.AddPlayedIndicator, + options.PercentPlayed, + options.UnplayedCount, + options.Blur, + options.BackgroundColor, + options.ForegroundLayer); + + try + { + if (!File.Exists(cacheFilePath)) + { + string resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, cacheFilePath, autoOrient, orientation, quality, options, outputFormat); + + if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase)) + { + return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); + } + } + + return (cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath)); + } + catch (Exception ex) + { + // If it fails for whatever reason, return the original image + _logger.LogError(ex, "Error encoding image"); + return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); + } + } + + private ImageFormat GetOutputFormat(IReadOnlyCollection clientSupportedFormats, bool requiresTransparency) + { + var serverFormats = GetSupportedImageOutputFormats(); + + // Client doesn't care about format, so start with webp if supported + if (serverFormats.Contains(ImageFormat.Webp) && clientSupportedFormats.Contains(ImageFormat.Webp)) + { + return ImageFormat.Webp; + } + + // If transparency is needed and webp isn't supported, than png is the only option + if (requiresTransparency && clientSupportedFormats.Contains(ImageFormat.Png)) + { + return ImageFormat.Png; + } + + foreach (var format in clientSupportedFormats) + { + if (serverFormats.Contains(format)) + { + return format; + } + } + + // We should never actually get here + return ImageFormat.Jpg; + } + + private string GetMimeType(ImageFormat format, string path) + => format switch + { + ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"), + ImageFormat.Gif => MimeTypes.GetMimeType("i.gif"), + ImageFormat.Jpg => MimeTypes.GetMimeType("i.jpg"), + ImageFormat.Png => MimeTypes.GetMimeType("i.png"), + ImageFormat.Webp => MimeTypes.GetMimeType("i.webp"), + _ => MimeTypes.GetMimeType(path) + }; + + /// + /// Gets the cache file path based on a set of parameters. + /// + private string GetCacheFilePath( + string originalPath, + int? width, + int? height, + int? maxWidth, + int? maxHeight, + int? fillWidth, + int? fillHeight, + int quality, + DateTime dateModified, + ImageFormat format, + bool addPlayedIndicator, + double percentPlayed, + int? unwatchedCount, + int? blur, + string backgroundColor, + string foregroundLayer) + { + var filename = new StringBuilder(256); + filename.Append(originalPath); + + filename.Append(",quality="); + filename.Append(quality); + + filename.Append(",datemodified="); + filename.Append(dateModified.Ticks); + + filename.Append(",f="); + filename.Append(format); + + if (width.HasValue) + { + filename.Append(",width="); + filename.Append(width.Value); + } + + if (height.HasValue) + { + filename.Append(",height="); + filename.Append(height.Value); + } + + if (maxWidth.HasValue) + { + filename.Append(",maxwidth="); + filename.Append(maxWidth.Value); + } + + if (maxHeight.HasValue) + { + filename.Append(",maxheight="); + filename.Append(maxHeight.Value); + } + + if (fillWidth.HasValue) + { + filename.Append(",fillwidth="); + filename.Append(fillWidth.Value); + } + + if (fillHeight.HasValue) + { + filename.Append(",fillheight="); + filename.Append(fillHeight.Value); + } + + if (addPlayedIndicator) + { + filename.Append(",pl=true"); + } + + if (percentPlayed > 0) + { + filename.Append(",p="); + filename.Append(percentPlayed); + } + + if (unwatchedCount.HasValue) + { + filename.Append(",p="); + filename.Append(unwatchedCount.Value); + } + + if (blur.HasValue) + { + filename.Append(",blur="); + filename.Append(blur.Value); + } + + if (!string.IsNullOrEmpty(backgroundColor)) + { + filename.Append(",b="); + filename.Append(backgroundColor); + } + + if (!string.IsNullOrEmpty(foregroundLayer)) + { + filename.Append(",fl="); + filename.Append(foregroundLayer); + } + + filename.Append(",v="); + filename.Append(Version); + + return GetCachePath(ResizedImageCachePath, filename.ToString(), "." + format.ToString().ToLowerInvariant()); + } + + /// + public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info) + { + int width = info.Width; + int height = info.Height; + + if (height > 0 && width > 0) + { + return new ImageDimensions(width, height); + } + + string path = info.Path; + _logger.LogDebug("Getting image size for item {ItemType} {Path}", item.GetType().Name, path); + + ImageDimensions size = GetImageDimensions(path); + info.Width = size.Width; + info.Height = size.Height; + + return size; + } + + /// + public ImageDimensions GetImageDimensions(string path) + => _imageEncoder.GetImageSize(path); + + /// + public string GetImageBlurHash(string path) + { + var size = GetImageDimensions(path); + return GetImageBlurHash(path, size); + } + + /// + public string GetImageBlurHash(string path, ImageDimensions imageDimensions) + { + if (imageDimensions.Width <= 0 || imageDimensions.Height <= 0) + { + return string.Empty; + } + + // We want tiles to be as close to square as possible, and to *mostly* keep under 16 tiles for performance. + // One tile is (width / xComp) x (height / yComp) pixels, which means that ideally yComp = xComp * height / width. + // See more at https://github.com/woltapp/blurhash/#how-do-i-pick-the-number-of-x-and-y-components + float xCompF = MathF.Sqrt(16.0f * imageDimensions.Width / imageDimensions.Height); + float yCompF = xCompF * imageDimensions.Height / imageDimensions.Width; + + int xComp = Math.Min((int)xCompF + 1, 9); + int yComp = Math.Min((int)yCompF + 1, 9); + + return _imageEncoder.GetImageBlurHash(xComp, yComp, path); + } + + /// + public string GetImageCacheTag(BaseItem item, ItemImageInfo image) + => (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture); + + /// + public string GetImageCacheTag(BaseItem item, ChapterInfo chapter) + { + return GetImageCacheTag(item, new ItemImageInfo + { + Path = chapter.ImagePath, + Type = ImageType.Chapter, + DateModified = chapter.ImageDateModified + }); + } + + /// + public string? GetImageCacheTag(User user) + { + if (user.ProfileImage is null) + { + return null; + } + + return (user.ProfileImage.Path + user.ProfileImage.LastModified.Ticks).GetMD5() + .ToString("N", CultureInfo.InvariantCulture); + } + + private Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified) + { + var inputFormat = Path.GetExtension(originalImagePath.AsSpan()).TrimStart('.').ToString(); + + // These are just jpg files renamed as tbn + if (string.Equals(inputFormat, "tbn", StringComparison.OrdinalIgnoreCase)) + { + return Task.FromResult((originalImagePath, dateModified)); + } + + // TODO _mediaEncoder.ConvertImage is not implemented + // if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat)) + // { + // try + // { + // string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture); + // + // string cacheExtension = _mediaEncoder.SupportsEncoder("libwebp") ? ".webp" : ".png"; + // var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension); + // + // var file = _fileSystem.GetFileInfo(outputPath); + // if (!file.Exists) + // { + // await _mediaEncoder.ConvertImage(originalImagePath, outputPath).ConfigureAwait(false); + // dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath); + // } + // else + // { + // dateModified = file.LastWriteTimeUtc; + // } + // + // originalImagePath = outputPath; + // } + // catch (Exception ex) + // { + // _logger.LogError(ex, "Image conversion failed for {Path}", originalImagePath); + // } + // } + + return Task.FromResult((originalImagePath, dateModified)); + } + + /// + /// Gets the cache path. + /// + /// The path. + /// Name of the unique. + /// The file extension. + /// System.String. + /// + /// path + /// or + /// uniqueName + /// or + /// fileExtension. + /// + public string GetCachePath(string path, string uniqueName, string fileExtension) + { + ArgumentException.ThrowIfNullOrEmpty(path); + ArgumentException.ThrowIfNullOrEmpty(uniqueName); + ArgumentException.ThrowIfNullOrEmpty(fileExtension); + + var filename = uniqueName.GetMD5() + fileExtension; + + return GetCachePath(path, filename); + } + + /// + /// Gets the cache path. + /// + /// The path. + /// The filename. + /// System.String. + /// + /// path + /// or + /// filename. + /// + public string GetCachePath(ReadOnlySpan path, ReadOnlySpan filename) + { + if (path.IsEmpty) + { + throw new ArgumentException("Path can't be empty.", nameof(path)); + } + + if (filename.IsEmpty) + { + throw new ArgumentException("Filename can't be empty.", nameof(filename)); + } + + var prefix = filename.Slice(0, 1); + + return Path.Join(path, prefix, filename); + } + + /// + public void CreateImageCollage(ImageCollageOptions options, string? libraryName) + { + _logger.LogInformation("Creating image collage and saving to {Path}", options.OutputPath); + + _imageEncoder.CreateImageCollage(options, libraryName); + + _logger.LogInformation("Completed creation of image collage and saved to {Path}", options.OutputPath); + } + + /// + public void Dispose() + { + if (_disposed) + { + return; + } + + if (_imageEncoder is IDisposable disposable) + { + disposable.Dispose(); + } + + _disposed = true; + } + } +} diff --git a/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj b/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj new file mode 100644 index 0000000000..a5bc8eaa7e --- /dev/null +++ b/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj @@ -0,0 +1,35 @@ + + + + + {08FFF49B-F175-4807-A2B5-73B0EBD9F716} + + + + net7.0 + false + true + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + diff --git a/src/Jellyfin.Drawing/NullImageEncoder.cs b/src/Jellyfin.Drawing/NullImageEncoder.cs new file mode 100644 index 0000000000..24dda108ec --- /dev/null +++ b/src/Jellyfin.Drawing/NullImageEncoder.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Model.Drawing; + +namespace Jellyfin.Drawing +{ + /// + /// A fallback implementation of . + /// + public class NullImageEncoder : IImageEncoder + { + /// + public IReadOnlyCollection SupportedInputFormats + => new HashSet(StringComparer.OrdinalIgnoreCase) { "png", "jpeg", "jpg" }; + + /// + public IReadOnlyCollection SupportedOutputFormats + => new HashSet() { ImageFormat.Jpg, ImageFormat.Png }; + + /// + public string Name => "Null Image Encoder"; + + /// + public bool SupportsImageCollageCreation => false; + + /// + public bool SupportsImageEncoding => false; + + /// + public ImageDimensions GetImageSize(string path) + => throw new NotImplementedException(); + + /// + public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat) + { + throw new NotImplementedException(); + } + + /// + public void CreateImageCollage(ImageCollageOptions options, string? libraryName) + { + throw new NotImplementedException(); + } + + /// + public void CreateSplashscreen(IReadOnlyList posters, IReadOnlyList backdrops) + { + throw new NotImplementedException(); + } + + /// + public string GetImageBlurHash(int xComp, int yComp, string path) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Jellyfin.Drawing/Properties/AssemblyInfo.cs b/src/Jellyfin.Drawing/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..3851bf9241 --- /dev/null +++ b/src/Jellyfin.Drawing/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Jellyfin.Drawing")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Jellyfin Project")] +[assembly: AssemblyProduct("Jellyfin Server")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("87b6f14e-16d8-4a58-a553-fd9945e47458")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// -- cgit v1.2.3 From 7516e61c5b1992a2488f73ca481b86fa079c04fa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 10 Jan 2023 14:50:35 +0000 Subject: chore(deps): update dotnet monorepo to v7.0.2 --- Emby.Server.Implementations/Emby.Server.Implementations.csproj | 2 +- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- .../Jellyfin.Server.Implementations.csproj | 8 ++++---- Jellyfin.Server/Jellyfin.Server.csproj | 4 ++-- MediaBrowser.Controller/MediaBrowser.Controller.csproj | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index b18a3174a0..7accc3b8ba 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -29,7 +29,7 @@ - + diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index eca68c6dfa..b5444138fb 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -13,7 +13,7 @@ - + diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 68e9205e40..c2c3513ad9 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -28,13 +28,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 2be628ac22..1cd65ea825 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -37,8 +37,8 @@ - - + + diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 4a66edb16f..6434621c46 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -19,7 +19,7 @@ - + -- cgit v1.2.3 From ab645653bc0b6fbc071832f8f27722225e6d5234 Mon Sep 17 00:00:00 2001 From: 0TTA Date: Tue, 10 Jan 2023 11:30:18 +0000 Subject: Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/ --- .../Localization/Core/ar.json | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index 4508363b0c..93d50e6e3b 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -3,9 +3,9 @@ "AppDeviceValues": "تطبيق: {0}, جهاز: {1}", "Application": "تطبيق", "Artists": "الفنانين", - "AuthenticationSucceededWithUserName": "تمت مصادقة {0} بنجاح", + "AuthenticationSucceededWithUserName": "نجحت عملية التوثيق بـ {0}", "Books": "الكتب", - "CameraImageUploadedFrom": "صورة كاميرا جديدة تم رفعها من {0}", + "CameraImageUploadedFrom": "رُفعت صورة الكاميرا الجديدة من {0}", "Channels": "القنوات", "ChapterNameValue": "الفصل {0}", "Collections": "التجميعات", @@ -16,7 +16,7 @@ "Folders": "المجلدات", "Genres": "التصنيفات", "HeaderAlbumArtists": "فناني الألبوم", - "HeaderContinueWatching": "استمر بالمشاهدة", + "HeaderContinueWatching": "استئناف المشاهدة", "HeaderFavoriteAlbums": "الألبومات المفضلة", "HeaderFavoriteArtists": "الفنانون المفضلون", "HeaderFavoriteEpisodes": "الحلقات المفضلة", @@ -27,15 +27,15 @@ "HeaderRecordingGroups": "مجموعات التسجيل", "HomeVideos": "الفيديوهات الشخصية", "Inherit": "توريث", - "ItemAddedWithName": "تم إضافة {0} للمكتبة", - "ItemRemovedWithName": "تم إزالة {0} من المكتبة", + "ItemAddedWithName": "أُضيف {0} للمكتبة", + "ItemRemovedWithName": "أُزيل {0} من المكتبة", "LabelIpAddressValue": "عنوان الآي بي: {0}", "LabelRunningTimeValue": "مدة التشغيل: {0}", "Latest": "أحدث", - "MessageApplicationUpdated": "لقد تم تحديث خادم Jellyfin", - "MessageApplicationUpdatedTo": "تم تحديث خادم Jellyfin الى {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "تم تحديث إعدادات الخادم في قسم {0}", - "MessageServerConfigurationUpdated": "تم تحديث إعدادات الخادم", + "MessageApplicationUpdated": "حُدث خادم Jellyfin", + "MessageApplicationUpdatedTo": "حُدث خادم Jellyfin إلى {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "حُدثت إعدادات الخادم في قسم {0}", + "MessageServerConfigurationUpdated": "حُدثت إعدادات الخادم", "MixedContent": "محتوى مختلط", "Movies": "الأفلام", "Music": "الموسيقى", @@ -45,14 +45,14 @@ "NameSeasonUnknown": "الموسم غير معروف", "NewVersionIsAvailable": "نسخة جديدة من خادم Jellyfin متوفرة للتحميل.", "NotificationOptionApplicationUpdateAvailable": "يوجد تحديث للتطبيق", - "NotificationOptionApplicationUpdateInstalled": "تم تحديث التطبيق", + "NotificationOptionApplicationUpdateInstalled": "نُصب تحديث التطبيق", "NotificationOptionAudioPlayback": "بدأ تشغيل المقطع الصوتي", - "NotificationOptionAudioPlaybackStopped": "تم إيقاف تشغيل المقطع الصوتي", - "NotificationOptionCameraImageUploaded": "تم رفع صورة الكاميرا", + "NotificationOptionAudioPlaybackStopped": "أُوقف تشغيل المقطع الصوتي", + "NotificationOptionCameraImageUploaded": "رُفعت صورة الكاميرا", "NotificationOptionInstallationFailed": "فشل في التثبيت", - "NotificationOptionNewLibraryContent": "تم إضافة محتوى جديد", + "NotificationOptionNewLibraryContent": "أُضيف محتوى جديدا", "NotificationOptionPluginError": "فشل في الملحق", - "NotificationOptionPluginInstalled": "تم تثبيت الملحق", + "NotificationOptionPluginInstalled": "ثُبتت المكونات الإضافية", "NotificationOptionPluginUninstalled": "تمت إزالة الملحق", "NotificationOptionPluginUpdateInstalled": "تم تثبيت تحديثات الملحق", "NotificationOptionServerRestartRequired": "يجب إعادة تشغيل الخادم", -- cgit v1.2.3 From d39dcb8ad2433a93524660a0cf47684a03f9cdd5 Mon Sep 17 00:00:00 2001 From: kshantum Date: Thu, 12 Jan 2023 04:05:02 +0000 Subject: Translated using Weblate (Russian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ru/ --- Emby.Server.Implementations/Localization/Core/ru.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json index dc45a8264d..65cf29e807 100644 --- a/Emby.Server.Implementations/Localization/Core/ru.json +++ b/Emby.Server.Implementations/Localization/Core/ru.json @@ -1,6 +1,6 @@ { "Albums": "Альбомы", - "AppDeviceValues": "Приложение.: {0}, Устройство.: {1}", + "AppDeviceValues": "Приложение: {0}, Устройство: {1}", "Application": "Приложение", "Artists": "Исполнители", "AuthenticationSucceededWithUserName": "{0} - авторизация успешна", @@ -50,7 +50,7 @@ "NotificationOptionAudioPlaybackStopped": "Воспроизведение аудио остановлено", "NotificationOptionCameraImageUploaded": "Изображения с камеры загружены", "NotificationOptionInstallationFailed": "Сбой установки", - "NotificationOptionNewLibraryContent": "Новое содержание добавлено", + "NotificationOptionNewLibraryContent": "Новое содержимое добавлено", "NotificationOptionPluginError": "Сбой плагина", "NotificationOptionPluginInstalled": "Плагин установлен", "NotificationOptionPluginUninstalled": "Плагин удалён", -- cgit v1.2.3 From 0571d3e403906167ce4204e63f8697f97e35d0bf Mon Sep 17 00:00:00 2001 From: Alex Popovic Date: Wed, 11 Jan 2023 22:25:39 +0000 Subject: Translated using Weblate (Serbian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sr/ --- Emby.Server.Implementations/Localization/Core/sr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/sr.json b/Emby.Server.Implementations/Localization/Core/sr.json index 1be8867f47..9739358df0 100644 --- a/Emby.Server.Implementations/Localization/Core/sr.json +++ b/Emby.Server.Implementations/Localization/Core/sr.json @@ -122,5 +122,6 @@ "TaskOptimizeDatabaseDescription": "Сажима базу података и скраћује слободан простор. Покретање овог задатка након скенирања библиотеке или других промена које подразумевају измене базе података које могу побољшати перформансе.", "External": "Спољно", "TaskKeyframeExtractorDescription": "Екстрактује кљулне сличице из видео датотека да би креирао више преицзну HLS плеј-листу. Овај задатак може да потраје дуже време.", - "TaskKeyframeExtractor": "Екстрактор кључних сличица" + "TaskKeyframeExtractor": "Екстрактор кључних сличица", + "HearingImpaired": "ослабљен слух" } -- cgit v1.2.3 From 37edb2188782fcae25175767e6713c64cf24b5a5 Mon Sep 17 00:00:00 2001 From: Napaul Intrarasing Date: Fri, 13 Jan 2023 15:11:16 +0000 Subject: Translated using Weblate (Thai) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/th/ --- Emby.Server.Implementations/Localization/Core/th.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/th.json b/Emby.Server.Implementations/Localization/Core/th.json index 9407a7b921..1a4fef64e8 100644 --- a/Emby.Server.Implementations/Localization/Core/th.json +++ b/Emby.Server.Implementations/Localization/Core/th.json @@ -120,5 +120,6 @@ "Forced": "บังคับใช้", "TaskOptimizeDatabase": "ปรับปรุงประสิทธิภาพฐานข้อมูล", "TaskOptimizeDatabaseDescription": "ลดขนาดการจัดเก็บฐานข้อมูล ใช้งานคำสั่งนี้หลังจากสแกนไลบรารีหรือหลังจากการเปลี่ยนแปลงฐานข้อมูล อาจจะทำให้ระบบทำงานเร็วขึ้น", - "External": "ภายนอก" + "External": "ภายนอก", + "HearingImpaired": "บกพร่องทางการได้ยิน" } -- cgit v1.2.3 From ab918c6292bb27a0e39f3d54f0f449b97246e59b Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 9 Jan 2023 00:07:53 +0100 Subject: Fine tune DB settings --- Emby.Server.Implementations/Data/BaseSqliteRepository.cs | 12 +++++++++++- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 2 -- Jellyfin.Server/Program.cs | 4 ---- 3 files changed, 11 insertions(+), 7 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index ff9aa4c2a3..230f633a7c 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -60,11 +60,16 @@ namespace Emby.Server.Implementations.Data /// The cache size or null. protected virtual int? CacheSize => null; + /// + /// Gets the locking mode. . + /// + protected virtual string LockingMode => "EXCLUSIVE"; + /// /// Gets the journal mode. . /// /// The journal mode. - protected virtual string JournalMode => "TRUNCATE"; + protected virtual string JournalMode => "WAL"; /// /// Gets the page size. @@ -116,6 +121,11 @@ namespace Emby.Server.Implementations.Data WriteConnection.Execute("PRAGMA cache_size=" + CacheSize.Value); } + if (!string.IsNullOrWhiteSpace(LockingMode)) + { + WriteConnection.Execute("PRAGMA locking_mode=" + LockingMode); + } + if (!string.IsNullOrWhiteSpace(JournalMode)) { WriteConnection.Execute("PRAGMA journal_mode=" + JournalMode); diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 763ff77f10..fb6902c398 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -359,8 +359,6 @@ namespace Emby.Server.Implementations.Data string[] queries = { - "PRAGMA locking_mode=EXCLUSIVE", - "create table if not exists TypedBaseItems (guid GUID primary key NOT NULL, type TEXT NOT NULL, data BLOB NULL, ParentId GUID NULL, Path TEXT NULL)", "create table if not exists AncestorIds (ItemId GUID NOT NULL, AncestorId GUID NOT NULL, AncestorIdText TEXT NOT NULL, PRIMARY KEY (ItemId, AncestorId))", diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 1506530f0c..540375dce6 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -273,10 +273,6 @@ namespace Jellyfin.Server ServicePointManager.Expect100Continue = false; Batteries_V2.Init(); - if (raw.sqlite3_enable_shared_cache(1) != raw.SQLITE_OK) - { - _logger.LogWarning("Failed to enable shared cache for SQLite"); - } } /// -- cgit v1.2.3 From 73740f6c6ea8e75ed57ab27228e3796938a93c81 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 9 Jan 2023 14:14:19 +0100 Subject: Change synchronous_mode to normal --- Emby.Server.Implementations/Data/BaseSqliteRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 230f633a7c..acbccc6655 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -89,7 +89,7 @@ namespace Emby.Server.Implementations.Data /// /// The synchronous mode or null. /// - protected virtual SynchronousMode? Synchronous => null; + protected virtual SynchronousMode? Synchronous => SynchronousMode.Normal; /// /// Gets or sets the write lock. -- cgit v1.2.3 From 6a8d24d9e926bd7b8f54132bf769f92790d9d68a Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 10 Jan 2023 22:29:05 +0100 Subject: Set journal_size_limit --- Emby.Server.Implementations/Data/BaseSqliteRepository.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index acbccc6655..1d61667f86 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -71,6 +71,12 @@ namespace Emby.Server.Implementations.Data /// The journal mode. protected virtual string JournalMode => "WAL"; + /// + /// Gets the journal size limit. . + /// + /// The journal size limit. + protected virtual int? JournalSizeLimit => 0; + /// /// Gets the page size. /// @@ -131,6 +137,11 @@ namespace Emby.Server.Implementations.Data WriteConnection.Execute("PRAGMA journal_mode=" + JournalMode); } + if (JournalSizeLimit.HasValue) + { + WriteConnection.Execute("PRAGMA journal_size_limit=" + (int)JournalSizeLimit.Value); + } + if (Synchronous.HasValue) { WriteConnection.Execute("PRAGMA synchronous=" + (int)Synchronous.Value); -- cgit v1.2.3 From 8ff0cb1e9dc0ac53ed099e58efa5366c377e393a Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 14 Jan 2023 21:39:42 +0100 Subject: Remove AddPeopleQueryIndex migration --- .../Data/SqliteItemRepository.cs | 36 ++-------------- Jellyfin.Server/Migrations/MigrationRunner.cs | 1 - .../Migrations/Routines/AddPeopleQueryIndex.cs | 49 ---------------------- 3 files changed, 3 insertions(+), 83 deletions(-) delete mode 100644 Jellyfin.Server/Migrations/Routines/AddPeopleQueryIndex.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index fb6902c398..bc703fe90d 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -383,39 +383,6 @@ namespace Emby.Server.Implementations.Data string[] postQueries = { - // obsolete - "drop index if exists idx_TypedBaseItems", - "drop index if exists idx_mediastreams", - "drop index if exists idx_mediastreams1", - "drop index if exists idx_" + ChaptersTableName, - "drop index if exists idx_UserDataKeys1", - "drop index if exists idx_UserDataKeys2", - "drop index if exists idx_TypeTopParentId3", - "drop index if exists idx_TypeTopParentId2", - "drop index if exists idx_TypeTopParentId4", - "drop index if exists idx_Type", - "drop index if exists idx_TypeTopParentId", - "drop index if exists idx_GuidType", - "drop index if exists idx_TopParentId", - "drop index if exists idx_TypeTopParentId6", - "drop index if exists idx_ItemValues2", - "drop index if exists Idx_ProviderIds", - "drop index if exists idx_ItemValues3", - "drop index if exists idx_ItemValues4", - "drop index if exists idx_ItemValues5", - "drop index if exists idx_UserDataKeys3", - "drop table if exists UserDataKeys", - "drop table if exists ProviderIds", - "drop index if exists Idx_ProviderIds1", - "drop table if exists Images", - "drop index if exists idx_Images", - "drop index if exists idx_TypeSeriesPresentationUniqueKey", - "drop index if exists idx_SeriesPresentationUniqueKey", - "drop index if exists idx_TypeSeriesPresentationUniqueKey2", - "drop index if exists idx_AncestorIds3", - "drop index if exists idx_AncestorIds4", - "drop index if exists idx_AncestorIds2", - "create index if not exists idx_PathTypedBaseItems on TypedBaseItems(Path)", "create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)", @@ -456,6 +423,9 @@ namespace Emby.Server.Implementations.Data // Used to update inherited tags "create index if not exists idx_ItemValues8 on ItemValues(Type, ItemId, Value)", + + "CREATE INDEX IF NOT EXISTS idx_TypedBaseItemsUserDataKeyType ON TypedBaseItems(UserDataKey, Type)", + "CREATE INDEX IF NOT EXISTS idx_PeopleNameListOrder ON People(Name, ListOrder)" }; using (var connection = GetConnection()) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index e9a45c140f..23fb9e3708 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -38,7 +38,6 @@ namespace Jellyfin.Server.Migrations typeof(Routines.ReaddDefaultPluginRepository), typeof(Routines.MigrateDisplayPreferencesDb), typeof(Routines.RemoveDownloadImagesInAdvance), - typeof(Routines.AddPeopleQueryIndex), typeof(Routines.MigrateAuthenticationDb) }; diff --git a/Jellyfin.Server/Migrations/Routines/AddPeopleQueryIndex.cs b/Jellyfin.Server/Migrations/Routines/AddPeopleQueryIndex.cs deleted file mode 100644 index 6343c422d5..0000000000 --- a/Jellyfin.Server/Migrations/Routines/AddPeopleQueryIndex.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.IO; -using MediaBrowser.Controller; -using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; - -namespace Jellyfin.Server.Migrations.Routines -{ - /// - /// Migration to add table indexes to optimize the Persons query. - /// - public class AddPeopleQueryIndex : IMigrationRoutine - { - private const string DbFilename = "library.db"; - private readonly ILogger _logger; - private readonly IServerApplicationPaths _serverApplicationPaths; - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - /// Instance of the interface. - public AddPeopleQueryIndex(ILogger logger, IServerApplicationPaths serverApplicationPaths) - { - _logger = logger; - _serverApplicationPaths = serverApplicationPaths; - } - - /// - public Guid Id => new Guid("DE009B59-BAAE-428D-A810-F67762DC05B8"); - - /// - public string Name => "AddPeopleQueryIndex"; - - /// - public bool PerformOnNewInstall => true; - - /// - public void Perform() - { - var databasePath = Path.Join(_serverApplicationPaths.DataPath, DbFilename); - using var connection = SQLite3.Open(databasePath, ConnectionFlags.ReadWrite, null); - _logger.LogInformation("Creating index idx_TypedBaseItemsUserDataKeyType"); - connection.Execute("CREATE INDEX IF NOT EXISTS idx_TypedBaseItemsUserDataKeyType ON TypedBaseItems(UserDataKey, Type);"); - _logger.LogInformation("Creating index idx_PeopleNameListOrder"); - connection.Execute("CREATE INDEX IF NOT EXISTS idx_PeopleNameListOrder ON People(Name, ListOrder);"); - } - } -} -- cgit v1.2.3 From dc85d86ea1db7608368eadbfee80f7618653f42d Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sun, 15 Jan 2023 15:39:57 -0500 Subject: Enable in-process restarting --- Emby.Server.Implementations/ApplicationHost.cs | 13 +--- Emby.Server.Implementations/IStartupOptions.cs | 10 --- .../Plugins/PluginManager.cs | 12 +++- Jellyfin.Server/Program.cs | 81 ++++++++-------------- Jellyfin.Server/StartupOptions.cs | 8 --- MediaBrowser.Common/IApplicationHost.cs | 6 -- MediaBrowser.Common/Plugins/IPluginManager.cs | 5 ++ 7 files changed, 46 insertions(+), 89 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 7b3d07dfc1..7b40f530c9 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -193,11 +193,6 @@ namespace Emby.Server.Implementations /// private string PublishedServerUrl => _startupConfig[AddressOverrideKey]; - /// - /// Gets a value indicating whether this instance can self restart. - /// - public bool CanSelfRestart => _startupOptions.RestartPath is not null; - public bool CoreStartupHasCompleted { get; private set; } public virtual bool CanLaunchWebBrowser @@ -935,17 +930,13 @@ namespace Emby.Server.Implementations /// public void Restart() { - if (!CanSelfRestart) - { - throw new PlatformNotSupportedException("The server is unable to self-restart. Please restart manually."); - } - if (IsShuttingDown) { return; } IsShuttingDown = true; + _pluginManager.UnloadAssemblies(); Task.Run(async () => { @@ -1047,7 +1038,7 @@ namespace Emby.Server.Implementations CachePath = ApplicationPaths.CachePath, OperatingSystem = MediaBrowser.Common.System.OperatingSystem.Id.ToString(), OperatingSystemDisplayName = MediaBrowser.Common.System.OperatingSystem.Name, - CanSelfRestart = CanSelfRestart, + CanSelfRestart = true, CanLaunchWebBrowser = CanLaunchWebBrowser, TranscodingTempPath = ConfigurationManager.GetTranscodePath(), ServerName = FriendlyName, diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index 3769ae4dd8..b7bcaace1b 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -20,16 +20,6 @@ namespace Emby.Server.Implementations /// string? PackageName { get; } - /// - /// Gets the value of the --restartpath command line option. - /// - string? RestartPath { get; } - - /// - /// Gets the value of the --restartargs command line option. - /// - string? RestartArgs { get; } - /// /// Gets the value of the --published-server-url command line option. /// diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 14e7c22696..6ef66f2b5d 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Net.Http; using System.Reflection; +using System.Runtime.Loader; using System.Text; using System.Text.Json; using System.Threading.Tasks; @@ -30,6 +31,7 @@ namespace Emby.Server.Implementations.Plugins { private readonly string _pluginsPath; private readonly Version _appVersion; + private readonly AssemblyLoadContext _assemblyLoadContext; private readonly JsonSerializerOptions _jsonOptions; private readonly ILogger _logger; private readonly IApplicationHost _appHost; @@ -76,6 +78,8 @@ namespace Emby.Server.Implementations.Plugins _appHost = appHost; _minimumVersion = new Version(0, 0, 0, 1); _plugins = Directory.Exists(_pluginsPath) ? DiscoverPlugins().ToList() : new List(); + + _assemblyLoadContext = new AssemblyLoadContext("PluginContext", true); } private IHttpClientFactory HttpClientFactory @@ -124,7 +128,7 @@ namespace Emby.Server.Implementations.Plugins Assembly assembly; try { - assembly = Assembly.LoadFrom(file); + assembly = _assemblyLoadContext.LoadFromAssemblyPath(file); // Load all required types to verify that the plugin will load assembly.GetTypes(); @@ -156,6 +160,12 @@ namespace Emby.Server.Implementations.Plugins } } + /// + public void UnloadAssemblies() + { + _assemblyLoadContext.Unload(); + } + /// /// Creates all the plugin instances. /// diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index b817ea6275..dded20347b 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -12,6 +12,7 @@ using Jellyfin.Server.Extensions; using Jellyfin.Server.Helpers; using Jellyfin.Server.Implementations; using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -40,8 +41,9 @@ namespace Jellyfin.Server /// public const string LoggingConfigFileSystem = "logging.json"; - private static readonly CancellationTokenSource _tokenSource = new CancellationTokenSource(); private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory(); + private static CancellationTokenSource _tokenSource = new(); + private static long _startTimestamp; private static ILogger _logger = NullLogger.Instance; private static bool _restartOnShutdown; @@ -86,11 +88,11 @@ namespace Jellyfin.Server private static async Task StartApp(StartupOptions options) { - var startTimestamp = Stopwatch.GetTimestamp(); + _startTimestamp = Stopwatch.GetTimestamp(); // Log all uncaught exceptions to std error static void UnhandledExceptionToConsole(object sender, UnhandledExceptionEventArgs e) => - Console.Error.WriteLine("Unhandled Exception\n" + e.ExceptionObject.ToString()); + Console.Error.WriteLine("Unhandled Exception\n" + e.ExceptionObject); AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionToConsole; ServerApplicationPaths appPaths = StartupHelpers.CreateApplicationPaths(options); @@ -151,14 +153,14 @@ namespace Jellyfin.Server // If hosting the web client, validate the client content path if (startupConfig.HostWebClient()) { - string? webContentPath = appPaths.WebPath; + var webContentPath = appPaths.WebPath; if (!Directory.Exists(webContentPath) || !Directory.EnumerateFiles(webContentPath).Any()) { _logger.LogError( "The server is expected to host the web client, but the provided content directory is either " + "invalid or empty: {WebContentPath}. If you do not want to host the web client with the " + "server, you may set the '--nowebclient' command line flag, or set" + - "'{ConfigKey}=false' in your config settings.", + "'{ConfigKey}=false' in your config settings", webContentPath, HostWebClientKey); Environment.ExitCode = 1; @@ -169,15 +171,31 @@ namespace Jellyfin.Server StartupHelpers.PerformStaticInitialization(); Migrations.MigrationRunner.RunPreStartup(appPaths, _loggerFactory); + do + { + _restartOnShutdown = false; + await StartServer(appPaths, options, startupConfig).ConfigureAwait(false); + + if (_restartOnShutdown) + { + _tokenSource = new CancellationTokenSource(); + _startTimestamp = Stopwatch.GetTimestamp(); + } + } while (_restartOnShutdown); + } + + private static async Task StartServer(IServerApplicationPaths appPaths, StartupOptions options, IConfiguration startupConfig) + { var appHost = new CoreAppHost( appPaths, _loggerFactory, options, startupConfig); + IHost? host = null; try { - var host = Host.CreateDefaultBuilder() + host = Host.CreateDefaultBuilder() .ConfigureServices(services => appHost.Init(services)) .ConfigureWebHostDefaults(webHostBuilder => webHostBuilder.ConfigureWebHostBuilder(appHost, startupConfig, appPaths, _logger)) .ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(options, appPaths, startupConfig)) @@ -203,13 +221,13 @@ namespace Jellyfin.Server } catch (Exception ex) when (ex is not TaskCanceledException) { - _logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in network.xml and try again."); + _logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in network.xml and try again"); throw; } await appHost.RunStartupTasksAsync(_tokenSource.Token).ConfigureAwait(false); - _logger.LogInformation("Startup complete {Time:g}", Stopwatch.GetElapsedTime(startTimestamp)); + _logger.LogInformation("Startup complete {Time:g}", Stopwatch.GetElapsedTime(_startTimestamp)); // Block main thread until shutdown await Task.Delay(-1, _tokenSource.Token).ConfigureAwait(false); @@ -220,7 +238,7 @@ namespace Jellyfin.Server } catch (Exception ex) { - _logger.LogCritical(ex, "Error while starting server."); + _logger.LogCritical(ex, "Error while starting server"); } finally { @@ -240,11 +258,7 @@ namespace Jellyfin.Server } await appHost.DisposeAsync().ConfigureAwait(false); - } - - if (_restartOnShutdown) - { - StartNewInstance(options); + host?.Dispose(); } } @@ -282,44 +296,5 @@ namespace Jellyfin.Server .AddEnvironmentVariables("JELLYFIN_") .AddInMemoryCollection(commandLineOpts.ConvertToConfig()); } - - private static void StartNewInstance(StartupOptions options) - { - _logger.LogInformation("Starting new instance"); - - var module = options.RestartPath; - - if (string.IsNullOrWhiteSpace(module)) - { - module = Environment.GetCommandLineArgs()[0]; - } - - string commandLineArgsString; - if (options.RestartArgs is not null) - { - commandLineArgsString = options.RestartArgs; - } - else - { - commandLineArgsString = string.Join( - ' ', - Environment.GetCommandLineArgs().Skip(1).Select(NormalizeCommandLineArgument)); - } - - _logger.LogInformation("Executable: {0}", module); - _logger.LogInformation("Arguments: {0}", commandLineArgsString); - - Process.Start(module, commandLineArgsString); - } - - private static string NormalizeCommandLineArgument(string arg) - { - if (!arg.Contains(' ', StringComparison.Ordinal)) - { - return arg; - } - - return "\"" + arg + "\""; - } } } diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index 0d9f379e0e..c3989751ca 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -63,14 +63,6 @@ namespace Jellyfin.Server [Option("package-name", Required = false, HelpText = "Used when packaging Jellyfin (example, synology).")] public string? PackageName { get; set; } - /// - [Option("restartpath", Required = false, HelpText = "Path to restart script.")] - public string? RestartPath { get; set; } - - /// - [Option("restartargs", Required = false, HelpText = "Arguments for restart script.")] - public string? RestartArgs { get; set; } - /// [Option("published-server-url", Required = false, HelpText = "Jellyfin Server URL to publish via auto discover process")] public string? PublishedServerUrl { get; set; } diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index 53683cdbdf..96ee701b38 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -47,12 +47,6 @@ namespace MediaBrowser.Common /// true if this instance is shutting down; otherwise, false. bool IsShuttingDown { get; } - /// - /// Gets a value indicating whether this instance can self restart. - /// - /// true if this instance can self restart; otherwise, false. - bool CanSelfRestart { get; } - /// /// Gets the application version. /// diff --git a/MediaBrowser.Common/Plugins/IPluginManager.cs b/MediaBrowser.Common/Plugins/IPluginManager.cs index 176bcbbd54..fa92d383a2 100644 --- a/MediaBrowser.Common/Plugins/IPluginManager.cs +++ b/MediaBrowser.Common/Plugins/IPluginManager.cs @@ -29,6 +29,11 @@ namespace MediaBrowser.Common.Plugins /// An IEnumerable{Assembly}. IEnumerable LoadAssemblies(); + /// + /// Unloads all of the assemblies. + /// + void UnloadAssemblies(); + /// /// Registers the plugin's services with the DI. /// Note: DI is not yet instantiated yet. -- cgit v1.2.3 From a48f18887468015876a8b056f15b68d6ef49ce04 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sun, 15 Jan 2023 17:00:38 -0500 Subject: Use separate assembly load contexts per plugin --- Emby.Server.Implementations/Plugins/PluginManager.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 6ef66f2b5d..3be20e7e3f 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -31,7 +31,7 @@ namespace Emby.Server.Implementations.Plugins { private readonly string _pluginsPath; private readonly Version _appVersion; - private readonly AssemblyLoadContext _assemblyLoadContext; + private readonly List _assemblyLoadContexts; private readonly JsonSerializerOptions _jsonOptions; private readonly ILogger _logger; private readonly IApplicationHost _appHost; @@ -79,7 +79,7 @@ namespace Emby.Server.Implementations.Plugins _minimumVersion = new Version(0, 0, 0, 1); _plugins = Directory.Exists(_pluginsPath) ? DiscoverPlugins().ToList() : new List(); - _assemblyLoadContext = new AssemblyLoadContext("PluginContext", true); + _assemblyLoadContexts = new List(); } private IHttpClientFactory HttpClientFactory @@ -128,7 +128,10 @@ namespace Emby.Server.Implementations.Plugins Assembly assembly; try { - assembly = _assemblyLoadContext.LoadFromAssemblyPath(file); + var assemblyLoadContext = new AssemblyLoadContext($"{plugin.Name} ${plugin.Version}", true); + _assemblyLoadContexts.Add(assemblyLoadContext); + + assembly = assemblyLoadContext.LoadFromAssemblyPath(file); // Load all required types to verify that the plugin will load assembly.GetTypes(); @@ -163,7 +166,10 @@ namespace Emby.Server.Implementations.Plugins /// public void UnloadAssemblies() { - _assemblyLoadContext.Unload(); + foreach (var assemblyLoadContext in _assemblyLoadContexts) + { + assemblyLoadContext.Unload(); + } } /// -- cgit v1.2.3 From 577d396649f44a26f9f8bf291a58994d414e23a6 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sun, 15 Jan 2023 17:35:36 -0500 Subject: Use custom plugin assembly load context --- .../Plugins/PluginLoadContext.cs | 33 ++++++++++++++++++++++ .../Plugins/PluginManager.cs | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 Emby.Server.Implementations/Plugins/PluginLoadContext.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Plugins/PluginLoadContext.cs b/Emby.Server.Implementations/Plugins/PluginLoadContext.cs new file mode 100644 index 0000000000..d04e9cf685 --- /dev/null +++ b/Emby.Server.Implementations/Plugins/PluginLoadContext.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.Loader; + +namespace Emby.Server.Implementations.Plugins; + +/// +/// A custom for loading Jellyfin plugins. +/// +public class PluginLoadContext : AssemblyLoadContext +{ + private readonly AssemblyDependencyResolver _resolver; + + /// + /// Initializes a new instance of the class. + /// + /// The path of the plugin assembly. + public PluginLoadContext(string path) : base(true) + { + _resolver = new AssemblyDependencyResolver(path); + } + + /// + protected override Assembly? Load(AssemblyName assemblyName) + { + var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); + if (assemblyPath is not null) + { + return LoadFromAssemblyPath(assemblyPath); + } + + return null; + } +} diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 3be20e7e3f..f2212f4dcb 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -128,7 +128,7 @@ namespace Emby.Server.Implementations.Plugins Assembly assembly; try { - var assemblyLoadContext = new AssemblyLoadContext($"{plugin.Name} ${plugin.Version}", true); + var assemblyLoadContext = new PluginLoadContext(file); _assemblyLoadContexts.Add(assemblyLoadContext); assembly = assemblyLoadContext.LoadFromAssemblyPath(file); -- cgit v1.2.3 From 8479f0f90cd8b7180f45340a59d7122755987859 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Mon, 16 Jan 2023 12:14:44 -0500 Subject: Rename JellyfinDb to JellyfinDbContext --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- .../ScheduledTasks/Tasks/OptimizeDatabaseTask.cs | 4 +- .../Activity/ActivityManager.cs | 4 +- .../Devices/DeviceManager.cs | 4 +- .../Extensions/ServiceCollectionExtensions.cs | 2 +- Jellyfin.Server.Implementations/JellyfinDb.cs | 188 --------------------- .../JellyfinDbContext.cs | 188 +++++++++++++++++++++ .../20200514181226_AddActivityLog.Designer.cs | 2 +- .../Migrations/20200613202153_AddUsers.Designer.cs | 2 +- ...0200728005145_AddDisplayPreferences.Designer.cs | 2 +- ...05220533_FixDisplayPreferencesIndex.Designer.cs | 2 +- ...20201004171403_AddMaxActiveSessions.Designer.cs | 2 +- ...4223655_AddCustomDisplayPreferences.Designer.cs | 2 +- ...10320181425_AddIndexesAndCollations.Designer.cs | 2 +- ...10407110544_NullableCustomPrefValue.Designer.cs | 2 +- .../20210814002109_AddDevices.Designer.cs | 2 +- ...052_AddIndexActivityLogsDateCreated.Designer.cs | 2 +- .../Migrations/DesignTimeJellyfinDbFactory.cs | 10 +- .../Migrations/JellyfinDbModelSnapshot.cs | 2 +- .../Security/AuthenticationManager.cs | 4 +- .../Security/AuthorizationContext.cs | 4 +- .../Users/DisplayPreferencesManager.cs | 4 +- .../Users/UserManager.cs | 8 +- Jellyfin.Server/CoreAppHost.cs | 2 +- .../Migrations/Routines/MigrateActivityLogDb.cs | 4 +- .../Migrations/Routines/MigrateAuthenticationDb.cs | 4 +- .../Routines/MigrateDisplayPreferencesDb.cs | 4 +- .../Migrations/Routines/MigrateUserDb.cs | 4 +- Jellyfin.Server/Program.cs | 2 +- Jellyfin.Server/Startup.cs | 2 +- 30 files changed, 233 insertions(+), 233 deletions(-) delete mode 100644 Jellyfin.Server.Implementations/JellyfinDb.cs create mode 100644 Jellyfin.Server.Implementations/JellyfinDbContext.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 7b3d07dfc1..0b8a314192 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -654,7 +654,7 @@ namespace Emby.Server.Implementations /// A task representing the service initialization operation. public async Task InitializeServices() { - var jellyfinDb = await Resolve>().CreateDbContextAsync().ConfigureAwait(false); + var jellyfinDb = await Resolve>().CreateDbContextAsync().ConfigureAwait(false); await using (jellyfinDb.ConfigureAwait(false)) { if ((await jellyfinDb.Database.GetPendingMigrationsAsync().ConfigureAwait(false)).Any()) diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs index 1efacd8562..1f3cb9b63f 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks { private readonly ILogger _logger; private readonly ILocalizationManager _localization; - private readonly IDbContextFactory _provider; + private readonly IDbContextFactory _provider; /// /// Initializes a new instance of the class. @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks public OptimizeDatabaseTask( ILogger logger, ILocalizationManager localization, - IDbContextFactory provider) + IDbContextFactory provider) { _logger = logger; _localization = localization; diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs index fc03cd6ae4..ce1c54cbb2 100644 --- a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs +++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs @@ -15,13 +15,13 @@ namespace Jellyfin.Server.Implementations.Activity /// public class ActivityManager : IActivityManager { - private readonly IDbContextFactory _provider; + private readonly IDbContextFactory _provider; /// /// Initializes a new instance of the class. /// /// The Jellyfin database provider. - public ActivityManager(IDbContextFactory provider) + public ActivityManager(IDbContextFactory provider) { _provider = provider; } diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs index a9b974be0e..8b15d6823d 100644 --- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs +++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs @@ -23,7 +23,7 @@ namespace Jellyfin.Server.Implementations.Devices /// public class DeviceManager : IDeviceManager { - private readonly IDbContextFactory _dbProvider; + private readonly IDbContextFactory _dbProvider; private readonly IUserManager _userManager; private readonly ConcurrentDictionary _capabilitiesMap = new(); @@ -32,7 +32,7 @@ namespace Jellyfin.Server.Implementations.Devices /// /// The database provider. /// The user manager. - public DeviceManager(IDbContextFactory dbProvider, IUserManager userManager) + public DeviceManager(IDbContextFactory dbProvider, IUserManager userManager) { _dbProvider = dbProvider; _userManager = userManager; diff --git a/Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs b/Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs index 05c6229316..de64911b42 100644 --- a/Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs +++ b/Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs @@ -29,7 +29,7 @@ public static class ServiceCollectionExtensions .SkipCachingResults(result => result.Value is null || (result.Value is EFTableRows rows && rows.RowsCount == 0))); - serviceCollection.AddPooledDbContextFactory((serviceProvider, opt) => + serviceCollection.AddPooledDbContextFactory((serviceProvider, opt) => { var applicationPaths = serviceProvider.GetRequiredService(); var loggerFactory = serviceProvider.GetRequiredService(); diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs deleted file mode 100644 index 064118ca54..0000000000 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ /dev/null @@ -1,188 +0,0 @@ -using System; -using System.Linq; -using Jellyfin.Data.Entities; -using Jellyfin.Data.Entities.Security; -using Jellyfin.Data.Interfaces; -using Microsoft.EntityFrameworkCore; - -namespace Jellyfin.Server.Implementations; - -/// -public class JellyfinDb : DbContext -{ - /// - /// Initializes a new instance of the class. - /// - /// The database context options. - public JellyfinDb(DbContextOptions options) : base(options) - { - } - - /// - /// Gets the containing the access schedules. - /// - public DbSet AccessSchedules => Set(); - - /// - /// Gets the containing the activity logs. - /// - public DbSet ActivityLogs => Set(); - - /// - /// Gets the containing the API keys. - /// - public DbSet ApiKeys => Set(); - - /// - /// Gets the containing the devices. - /// - public DbSet Devices => Set(); - - /// - /// Gets the containing the device options. - /// - public DbSet DeviceOptions => Set(); - - /// - /// Gets the containing the display preferences. - /// - public DbSet DisplayPreferences => Set(); - - /// - /// Gets the containing the image infos. - /// - public DbSet ImageInfos => Set(); - - /// - /// Gets the containing the item display preferences. - /// - public DbSet ItemDisplayPreferences => Set(); - - /// - /// Gets the containing the custom item display preferences. - /// - public DbSet CustomItemDisplayPreferences => Set(); - - /// - /// Gets the containing the permissions. - /// - public DbSet Permissions => Set(); - - /// - /// Gets the containing the preferences. - /// - public DbSet Preferences => Set(); - - /// - /// Gets the containing the users. - /// - public DbSet Users => Set(); - - /*public DbSet Artwork => Set(); - - public DbSet Books => Set(); - - public DbSet BookMetadata => Set(); - - public DbSet Chapters => Set(); - - public DbSet Collections => Set(); - - public DbSet CollectionItems => Set(); - - public DbSet Companies => Set(); - - public DbSet CompanyMetadata => Set(); - - public DbSet CustomItems => Set(); - - public DbSet CustomItemMetadata => Set(); - - public DbSet Episodes => Set(); - - public DbSet EpisodeMetadata => Set(); - - public DbSet Genres => Set(); - - public DbSet Groups => Set(); - - public DbSet Libraries => Set(); - - public DbSet LibraryItems => Set(); - - public DbSet LibraryRoot => Set(); - - public DbSet MediaFiles => Set(); - - public DbSet MediaFileStream => Set(); - - public DbSet Metadata => Set(); - - public DbSet MetadataProviders => Set(); - - public DbSet MetadataProviderIds => Set(); - - public DbSet Movies => Set(); - - public DbSet MovieMetadata => Set(); - - public DbSet MusicAlbums => Set(); - - public DbSet MusicAlbumMetadata => Set(); - - public DbSet People => Set(); - - public DbSet PersonRoles => Set(); - - public DbSet Photo => Set(); - - public DbSet PhotoMetadata => Set(); - - public DbSet ProviderMappings => Set(); - - public DbSet Ratings => Set(); - - /// - /// Repository for global::Jellyfin.Data.Entities.RatingSource - This is the entity to - /// store review ratings, not age ratings. - /// - public DbSet RatingSources => Set(); - - public DbSet Releases => Set(); - - public DbSet Seasons => Set(); - - public DbSet SeasonMetadata => Set(); - - public DbSet Series => Set(); - - public DbSet SeriesMetadata => Set Tracks => Set(); - - public DbSet TrackMetadata => Set();*/ - - /// - public override int SaveChanges() - { - foreach (var saveEntity in ChangeTracker.Entries() - .Where(e => e.State == EntityState.Modified) - .Select(entry => entry.Entity) - .OfType()) - { - saveEntity.OnSavingChanges(); - } - - return base.SaveChanges(); - } - - /// - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.SetDefaultDateTimeKind(DateTimeKind.Utc); - base.OnModelCreating(modelBuilder); - - // Configuration for each entity is in it's own class inside 'ModelConfiguration'. - modelBuilder.ApplyConfigurationsFromAssembly(typeof(JellyfinDb).Assembly); - } -} diff --git a/Jellyfin.Server.Implementations/JellyfinDbContext.cs b/Jellyfin.Server.Implementations/JellyfinDbContext.cs new file mode 100644 index 0000000000..0d91707e3e --- /dev/null +++ b/Jellyfin.Server.Implementations/JellyfinDbContext.cs @@ -0,0 +1,188 @@ +using System; +using System.Linq; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Entities.Security; +using Jellyfin.Data.Interfaces; +using Microsoft.EntityFrameworkCore; + +namespace Jellyfin.Server.Implementations; + +/// +public class JellyfinDbContext : DbContext +{ + /// + /// Initializes a new instance of the class. + /// + /// The database context options. + public JellyfinDbContext(DbContextOptions options) : base(options) + { + } + + /// + /// Gets the containing the access schedules. + /// + public DbSet AccessSchedules => Set(); + + /// + /// Gets the containing the activity logs. + /// + public DbSet ActivityLogs => Set(); + + /// + /// Gets the containing the API keys. + /// + public DbSet ApiKeys => Set(); + + /// + /// Gets the containing the devices. + /// + public DbSet Devices => Set(); + + /// + /// Gets the containing the device options. + /// + public DbSet DeviceOptions => Set(); + + /// + /// Gets the containing the display preferences. + /// + public DbSet DisplayPreferences => Set(); + + /// + /// Gets the containing the image infos. + /// + public DbSet ImageInfos => Set(); + + /// + /// Gets the containing the item display preferences. + /// + public DbSet ItemDisplayPreferences => Set(); + + /// + /// Gets the containing the custom item display preferences. + /// + public DbSet CustomItemDisplayPreferences => Set(); + + /// + /// Gets the containing the permissions. + /// + public DbSet Permissions => Set(); + + /// + /// Gets the containing the preferences. + /// + public DbSet Preferences => Set(); + + /// + /// Gets the containing the users. + /// + public DbSet Users => Set(); + + /*public DbSet Artwork => Set(); + + public DbSet Books => Set(); + + public DbSet BookMetadata => Set(); + + public DbSet Chapters => Set(); + + public DbSet Collections => Set(); + + public DbSet CollectionItems => Set(); + + public DbSet Companies => Set(); + + public DbSet CompanyMetadata => Set(); + + public DbSet CustomItems => Set(); + + public DbSet CustomItemMetadata => Set(); + + public DbSet Episodes => Set(); + + public DbSet EpisodeMetadata => Set(); + + public DbSet Genres => Set(); + + public DbSet Groups => Set(); + + public DbSet Libraries => Set(); + + public DbSet LibraryItems => Set(); + + public DbSet LibraryRoot => Set(); + + public DbSet MediaFiles => Set(); + + public DbSet MediaFileStream => Set(); + + public DbSet Metadata => Set(); + + public DbSet MetadataProviders => Set(); + + public DbSet MetadataProviderIds => Set(); + + public DbSet Movies => Set(); + + public DbSet MovieMetadata => Set(); + + public DbSet MusicAlbums => Set(); + + public DbSet MusicAlbumMetadata => Set(); + + public DbSet People => Set(); + + public DbSet PersonRoles => Set(); + + public DbSet Photo => Set(); + + public DbSet PhotoMetadata => Set(); + + public DbSet ProviderMappings => Set(); + + public DbSet Ratings => Set(); + + /// + /// Repository for global::Jellyfin.Data.Entities.RatingSource - This is the entity to + /// store review ratings, not age ratings. + /// + public DbSet RatingSources => Set(); + + public DbSet Releases => Set(); + + public DbSet Seasons => Set(); + + public DbSet SeasonMetadata => Set(); + + public DbSet Series => Set(); + + public DbSet SeriesMetadata => Set Tracks => Set(); + + public DbSet TrackMetadata => Set();*/ + + /// + public override int SaveChanges() + { + foreach (var saveEntity in ChangeTracker.Entries() + .Where(e => e.State == EntityState.Modified) + .Select(entry => entry.Entity) + .OfType()) + { + saveEntity.OnSavingChanges(); + } + + return base.SaveChanges(); + } + + /// + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.SetDefaultDateTimeKind(DateTimeKind.Utc); + base.OnModelCreating(modelBuilder); + + // Configuration for each entity is in it's own class inside 'ModelConfiguration'. + modelBuilder.ApplyConfigurationsFromAssembly(typeof(JellyfinDbContext).Assembly); + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.Designer.cs index 98a83b7450..4be6c2faa3 100644 --- a/Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.Designer.cs +++ b/Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.Designer.cs @@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Jellyfin.Server.Implementations.Migrations { - [DbContext(typeof(JellyfinDb))] + [DbContext(typeof(JellyfinDbContext))] [Migration("20200514181226_AddActivityLog")] partial class AddActivityLog { diff --git a/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.Designer.cs index 6342ce9cf3..f3254734ad 100644 --- a/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.Designer.cs +++ b/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.Designer.cs @@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Jellyfin.Server.Implementations.Migrations { - [DbContext(typeof(JellyfinDb))] + [DbContext(typeof(JellyfinDbContext))] [Migration("20200613202153_AddUsers")] partial class AddUsers { diff --git a/Jellyfin.Server.Implementations/Migrations/20200728005145_AddDisplayPreferences.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200728005145_AddDisplayPreferences.Designer.cs index d44707d069..12d6faa8f5 100644 --- a/Jellyfin.Server.Implementations/Migrations/20200728005145_AddDisplayPreferences.Designer.cs +++ b/Jellyfin.Server.Implementations/Migrations/20200728005145_AddDisplayPreferences.Designer.cs @@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Jellyfin.Server.Implementations.Migrations { - [DbContext(typeof(JellyfinDb))] + [DbContext(typeof(JellyfinDbContext))] [Migration("20200728005145_AddDisplayPreferences")] partial class AddDisplayPreferences { diff --git a/Jellyfin.Server.Implementations/Migrations/20200905220533_FixDisplayPreferencesIndex.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200905220533_FixDisplayPreferencesIndex.Designer.cs index 2234f9d5fd..f1cc208058 100644 --- a/Jellyfin.Server.Implementations/Migrations/20200905220533_FixDisplayPreferencesIndex.Designer.cs +++ b/Jellyfin.Server.Implementations/Migrations/20200905220533_FixDisplayPreferencesIndex.Designer.cs @@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Jellyfin.Server.Implementations.Migrations { - [DbContext(typeof(JellyfinDb))] + [DbContext(typeof(JellyfinDbContext))] [Migration("20200905220533_FixDisplayPreferencesIndex")] partial class FixDisplayPreferencesIndex { diff --git a/Jellyfin.Server.Implementations/Migrations/20201004171403_AddMaxActiveSessions.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20201004171403_AddMaxActiveSessions.Designer.cs index e5c326a326..f134d363c8 100644 --- a/Jellyfin.Server.Implementations/Migrations/20201004171403_AddMaxActiveSessions.Designer.cs +++ b/Jellyfin.Server.Implementations/Migrations/20201004171403_AddMaxActiveSessions.Designer.cs @@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Jellyfin.Server.Implementations.Migrations { - [DbContext(typeof(JellyfinDb))] + [DbContext(typeof(JellyfinDbContext))] [Migration("20201004171403_AddMaxActiveSessions")] partial class AddMaxActiveSessions { diff --git a/Jellyfin.Server.Implementations/Migrations/20201204223655_AddCustomDisplayPreferences.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20201204223655_AddCustomDisplayPreferences.Designer.cs index 10663d0655..ec65205d45 100644 --- a/Jellyfin.Server.Implementations/Migrations/20201204223655_AddCustomDisplayPreferences.Designer.cs +++ b/Jellyfin.Server.Implementations/Migrations/20201204223655_AddCustomDisplayPreferences.Designer.cs @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Jellyfin.Server.Implementations.Migrations { - [DbContext(typeof(JellyfinDb))] + [DbContext(typeof(JellyfinDbContext))] [Migration("20201204223655_AddCustomDisplayPreferences")] partial class AddCustomDisplayPreferences { diff --git a/Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.Designer.cs index 8696768245..45dad6be68 100644 --- a/Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.Designer.cs +++ b/Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.Designer.cs @@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Jellyfin.Server.Implementations.Migrations { - [DbContext(typeof(JellyfinDb))] + [DbContext(typeof(JellyfinDbContext))] [Migration("20210320181425_AddIndexesAndCollations")] partial class AddIndexesAndCollations { diff --git a/Jellyfin.Server.Implementations/Migrations/20210407110544_NullableCustomPrefValue.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20210407110544_NullableCustomPrefValue.Designer.cs index d332d19f28..eff84b4574 100644 --- a/Jellyfin.Server.Implementations/Migrations/20210407110544_NullableCustomPrefValue.Designer.cs +++ b/Jellyfin.Server.Implementations/Migrations/20210407110544_NullableCustomPrefValue.Designer.cs @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Jellyfin.Server.Implementations.Migrations { - [DbContext(typeof(JellyfinDb))] + [DbContext(typeof(JellyfinDbContext))] [Migration("20210407110544_NullableCustomPrefValue")] partial class NullableCustomPrefValue { diff --git a/Jellyfin.Server.Implementations/Migrations/20210814002109_AddDevices.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20210814002109_AddDevices.Designer.cs index 7e9566e2ea..ad7c2dd2c9 100644 --- a/Jellyfin.Server.Implementations/Migrations/20210814002109_AddDevices.Designer.cs +++ b/Jellyfin.Server.Implementations/Migrations/20210814002109_AddDevices.Designer.cs @@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Jellyfin.Server.Implementations.Migrations { - [DbContext(typeof(JellyfinDb))] + [DbContext(typeof(JellyfinDbContext))] [Migration("20210814002109_AddDevices")] partial class AddDevices { diff --git a/Jellyfin.Server.Implementations/Migrations/20221022080052_AddIndexActivityLogsDateCreated.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20221022080052_AddIndexActivityLogsDateCreated.Designer.cs index 03e3f3c921..f9497a3b69 100644 --- a/Jellyfin.Server.Implementations/Migrations/20221022080052_AddIndexActivityLogsDateCreated.Designer.cs +++ b/Jellyfin.Server.Implementations/Migrations/20221022080052_AddIndexActivityLogsDateCreated.Designer.cs @@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Jellyfin.Server.Implementations.Migrations { - [DbContext(typeof(JellyfinDb))] + [DbContext(typeof(JellyfinDbContext))] [Migration("20221022080052_AddIndexActivityLogsDateCreated")] partial class AddIndexActivityLogsDateCreated { diff --git a/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs b/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs index 72a4a8c3b6..940cf7c5d5 100644 --- a/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs +++ b/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs @@ -4,17 +4,17 @@ using Microsoft.EntityFrameworkCore.Design; namespace Jellyfin.Server.Implementations.Migrations { /// - /// The design time factory for . + /// The design time factory for . /// This is only used for the creation of migrations and not during runtime. /// - internal class DesignTimeJellyfinDbFactory : IDesignTimeDbContextFactory + internal class DesignTimeJellyfinDbFactory : IDesignTimeDbContextFactory { - public JellyfinDb CreateDbContext(string[] args) + public JellyfinDbContext CreateDbContext(string[] args) { - var optionsBuilder = new DbContextOptionsBuilder(); + var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseSqlite("Data Source=jellyfin.db"); - return new JellyfinDb(optionsBuilder.Options); + return new JellyfinDbContext(optionsBuilder.Options); } } } diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs index 2dd7b094aa..dd5f7f0121 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Jellyfin.Server.Implementations.Migrations { - [DbContext(typeof(JellyfinDb))] + [DbContext(typeof(JellyfinDbContext))] partial class JellyfinDbModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) diff --git a/Jellyfin.Server.Implementations/Security/AuthenticationManager.cs b/Jellyfin.Server.Implementations/Security/AuthenticationManager.cs index 1b56237fe7..b2dfe60a14 100644 --- a/Jellyfin.Server.Implementations/Security/AuthenticationManager.cs +++ b/Jellyfin.Server.Implementations/Security/AuthenticationManager.cs @@ -10,13 +10,13 @@ namespace Jellyfin.Server.Implementations.Security /// public class AuthenticationManager : IAuthenticationManager { - private readonly IDbContextFactory _dbProvider; + private readonly IDbContextFactory _dbProvider; /// /// Initializes a new instance of the class. /// /// The database provider. - public AuthenticationManager(IDbContextFactory dbProvider) + public AuthenticationManager(IDbContextFactory dbProvider) { _dbProvider = dbProvider; } diff --git a/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs b/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs index ec5742bab0..63d3e8a04c 100644 --- a/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs +++ b/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs @@ -16,12 +16,12 @@ namespace Jellyfin.Server.Implementations.Security { public class AuthorizationContext : IAuthorizationContext { - private readonly IDbContextFactory _jellyfinDbProvider; + private readonly IDbContextFactory _jellyfinDbProvider; private readonly IUserManager _userManager; private readonly IServerApplicationHost _serverApplicationHost; public AuthorizationContext( - IDbContextFactory jellyfinDb, + IDbContextFactory jellyfinDb, IUserManager userManager, IServerApplicationHost serverApplicationHost) { diff --git a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs index 8936f57c62..bfae81e4ca 100644 --- a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs +++ b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs @@ -15,13 +15,13 @@ namespace Jellyfin.Server.Implementations.Users /// public class DisplayPreferencesManager : IDisplayPreferencesManager { - private readonly JellyfinDb _dbContext; + private readonly JellyfinDbContext _dbContext; /// /// Initializes a new instance of the class. /// /// The database context factory. - public DisplayPreferencesManager(IDbContextFactory dbContextFactory) + public DisplayPreferencesManager(IDbContextFactory dbContextFactory) { _dbContext = dbContextFactory.CreateDbContext(); } diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 266bff5e8d..dc9d78857e 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -33,7 +33,7 @@ namespace Jellyfin.Server.Implementations.Users /// public class UserManager : IUserManager { - private readonly IDbContextFactory _dbProvider; + private readonly IDbContextFactory _dbProvider; private readonly IEventManager _eventManager; private readonly ICryptoProvider _cryptoProvider; private readonly INetworkManager _networkManager; @@ -59,7 +59,7 @@ namespace Jellyfin.Server.Implementations.Users /// The image processor. /// The logger. public UserManager( - IDbContextFactory dbProvider, + IDbContextFactory dbProvider, IEventManager eventManager, ICryptoProvider cryptoProvider, INetworkManager networkManager, @@ -172,7 +172,7 @@ namespace Jellyfin.Server.Implementations.Users } } - internal async Task CreateUserInternalAsync(string name, JellyfinDb dbContext) + internal async Task CreateUserInternalAsync(string name, JellyfinDbContext dbContext) { // TODO: Remove after user item data is migrated. var max = await dbContext.Users.AsQueryable().AnyAsync().ConfigureAwait(false) @@ -886,7 +886,7 @@ namespace Jellyfin.Server.Implementations.Users await UpdateUserAsync(user).ConfigureAwait(false); } - private async Task UpdateUserInternalAsync(JellyfinDb dbContext, User user) + private async Task UpdateUserInternalAsync(JellyfinDbContext dbContext, User user) { dbContext.Users.Update(user); _users[user.Id] = user; diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index d70b8f3ab7..40cd5a0446 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -107,7 +107,7 @@ namespace Jellyfin.Server yield return typeof(CoreAppHost).Assembly; // Jellyfin.Server.Implementations - yield return typeof(JellyfinDb).Assembly; + yield return typeof(JellyfinDbContext).Assembly; } /// diff --git a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs index bf66f75ff9..e8a0af9f88 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs @@ -19,7 +19,7 @@ namespace Jellyfin.Server.Migrations.Routines private const string DbFilename = "activitylog.db"; private readonly ILogger _logger; - private readonly IDbContextFactory _provider; + private readonly IDbContextFactory _provider; private readonly IServerApplicationPaths _paths; /// @@ -28,7 +28,7 @@ namespace Jellyfin.Server.Migrations.Routines /// The logger. /// The server application paths. /// The database provider. - public MigrateActivityLogDb(ILogger logger, IServerApplicationPaths paths, IDbContextFactory provider) + public MigrateActivityLogDb(ILogger logger, IServerApplicationPaths paths, IDbContextFactory provider) { _logger = logger; _provider = provider; diff --git a/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs index bf1ea8233d..09daae0ff9 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs @@ -20,7 +20,7 @@ namespace Jellyfin.Server.Migrations.Routines private const string DbFilename = "authentication.db"; private readonly ILogger _logger; - private readonly IDbContextFactory _dbProvider; + private readonly IDbContextFactory _dbProvider; private readonly IServerApplicationPaths _appPaths; private readonly IUserManager _userManager; @@ -33,7 +33,7 @@ namespace Jellyfin.Server.Migrations.Routines /// The user manager. public MigrateAuthenticationDb( ILogger logger, - IDbContextFactory dbProvider, + IDbContextFactory dbProvider, IServerApplicationPaths appPaths, IUserManager userManager) { diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index 0fad77cfe6..4b692d14f0 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -25,7 +25,7 @@ namespace Jellyfin.Server.Migrations.Routines private readonly ILogger _logger; private readonly IServerApplicationPaths _paths; - private readonly IDbContextFactory _provider; + private readonly IDbContextFactory _provider; private readonly JsonSerializerOptions _jsonOptions; private readonly IUserManager _userManager; @@ -39,7 +39,7 @@ namespace Jellyfin.Server.Migrations.Routines public MigrateDisplayPreferencesDb( ILogger logger, IServerApplicationPaths paths, - IDbContextFactory provider, + IDbContextFactory provider, IUserManager userManager) { _logger = logger; diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index 2dbd82e8fd..ea2f033027 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -27,7 +27,7 @@ namespace Jellyfin.Server.Migrations.Routines private readonly ILogger _logger; private readonly IServerApplicationPaths _paths; - private readonly IDbContextFactory _provider; + private readonly IDbContextFactory _provider; private readonly IXmlSerializer _xmlSerializer; /// @@ -40,7 +40,7 @@ namespace Jellyfin.Server.Migrations.Routines public MigrateUserDb( ILogger logger, IServerApplicationPaths paths, - IDbContextFactory provider, + IDbContextFactory provider, IXmlSerializer xmlSerializer) { _logger = logger; diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 540375dce6..70d7a07011 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -236,7 +236,7 @@ namespace Jellyfin.Server { _logger.LogInformation("Running query planner optimizations in the database... This might take a while"); // Run before disposing the application - var context = await appHost.ServiceProvider.GetRequiredService>().CreateDbContextAsync().ConfigureAwait(false); + var context = await appHost.ServiceProvider.GetRequiredService>().CreateDbContextAsync().ConfigureAwait(false); await using (context.ConfigureAwait(false)) { if (context.Database.IsSqlite()) diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index f89f81c766..c5f20b205b 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -119,7 +119,7 @@ namespace Jellyfin.Server .ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate); services.AddHealthChecks() - .AddCheck>(nameof(JellyfinDb)); + .AddCheck>(nameof(JellyfinDbContext)); services.AddHlsPlaylistGenerator(); } -- cgit v1.2.3 From bf055a2f990d90272915bd7d6732bcaa09ac4fce Mon Sep 17 00:00:00 2001 From: Slug-Cat Date: Mon, 16 Jan 2023 06:57:34 +0000 Subject: Translated using Weblate (Pirate (pr)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pr/ --- Emby.Server.Implementations/Localization/Core/pr.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/pr.json b/Emby.Server.Implementations/Localization/Core/pr.json index 506c14fdc2..466c8a9905 100644 --- a/Emby.Server.Implementations/Localization/Core/pr.json +++ b/Emby.Server.Implementations/Localization/Core/pr.json @@ -13,5 +13,11 @@ "DeviceOfflineWithName": "{0} abandoned ship", "AppDeviceValues": "Captain: {0}, Ship: {1}", "CameraImageUploadedFrom": "Yer looking glass has glimpsed another painting from {0}", - "Collections": "Barrels" + "Collections": "Barrels", + "ItemAddedWithName": "{0} is now with yer treasure", + "Default": "Normal-like", + "FailedLoginAttemptWithUserName": "Ye failed to get in, try from {0}", + "Favorites": "Finest Loot", + "ItemRemovedWithName": "{0} was taken from yer treasure", + "LabelIpAddressValue": "Ship's coordinates: {0}" } -- cgit v1.2.3 From c59f2a3c46dd3fad42dd987e04e48d6576373b13 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 17 Jan 2023 14:41:10 -0500 Subject: Mark CanSelfRestart as Obsolete --- Emby.Server.Implementations/ApplicationHost.cs | 1 - MediaBrowser.Model/System/SystemInfo.cs | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 7b40f530c9..3db48e42f2 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1038,7 +1038,6 @@ namespace Emby.Server.Implementations CachePath = ApplicationPaths.CachePath, OperatingSystem = MediaBrowser.Common.System.OperatingSystem.Id.ToString(), OperatingSystemDisplayName = MediaBrowser.Common.System.OperatingSystem.Name, - CanSelfRestart = true, CanLaunchWebBrowser = CanLaunchWebBrowser, TranscodingTempPath = ConfigurationManager.GetTranscodePath(), ServerName = FriendlyName, diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index a82c1c8c0c..9e56849c7c 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -79,8 +79,9 @@ namespace MediaBrowser.Model.System /// /// Gets or sets a value indicating whether this instance can self restart. /// - /// true if this instance can self restart; otherwise, false. - public bool CanSelfRestart { get; set; } + /// true. + [Obsolete("This is always true")] + public bool CanSelfRestart { get; set; } = true; public bool CanLaunchWebBrowser { get; set; } -- cgit v1.2.3 From 971e338b71afe0675934700bd4ac3494f22e6afa Mon Sep 17 00:00:00 2001 From: SuperDumbTM Date: Tue, 17 Jan 2023 09:16:59 +0000 Subject: Translated using Weblate (Chinese (Traditional, Hong Kong)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hant_HK/ --- Emby.Server.Implementations/Localization/Core/zh-HK.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json index baa9ecc1c1..cdc25ec7c7 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-HK.json +++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json @@ -9,15 +9,15 @@ "Channels": "頻道", "ChapterNameValue": "章節 {0}", "Collections": "合輯", - "DeviceOfflineWithName": "{0} 已經斷開連結", + "DeviceOfflineWithName": "{0} 已經斷開連接", "DeviceOnlineWithName": "{0} 已經連接", - "FailedLoginAttemptWithUserName": "來自 {0} 的登入失敗", + "FailedLoginAttemptWithUserName": "{0} 登入失敗", "Favorites": "我的最愛", "Folders": "資料夾", "Genres": "風格", "HeaderAlbumArtists": "專輯藝人", "HeaderContinueWatching": "繼續觀看", - "HeaderFavoriteAlbums": "最愛專輯", + "HeaderFavoriteAlbums": "最愛的專輯", "HeaderFavoriteArtists": "最愛的藝人", "HeaderFavoriteEpisodes": "最愛的劇集", "HeaderFavoriteShows": "最愛的節目", @@ -44,10 +44,10 @@ "NameSeasonNumber": "第 {0} 季", "NameSeasonUnknown": "未知季數", "NewVersionIsAvailable": "新版本的 Jellyfin 伺服器可供下載。", - "NotificationOptionApplicationUpdateAvailable": "有可用的應用程式更新", + "NotificationOptionApplicationUpdateAvailable": "有可用的更新", "NotificationOptionApplicationUpdateInstalled": "應用程式已更新", - "NotificationOptionAudioPlayback": "開始播放音頻", - "NotificationOptionAudioPlaybackStopped": "已停止播放音頻", + "NotificationOptionAudioPlayback": "開始播放音訊", + "NotificationOptionAudioPlaybackStopped": "已停止播放音訊", "NotificationOptionCameraImageUploaded": "相片已上傳", "NotificationOptionInstallationFailed": "安裝失敗", "NotificationOptionNewLibraryContent": "已添加新内容", -- cgit v1.2.3 From e408da46518e98f0c46c794e75085a8c9417ab00 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Jan 2023 04:16:59 -0700 Subject: chore(deps): update dependency microsoft.codeanalysis.bannedapianalyzers to v3.3.4 (#9117) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Emby.Dlna/Emby.Dlna.csproj | 2 +- Emby.Naming/Emby.Naming.csproj | 2 +- Emby.Notifications/Emby.Notifications.csproj | 2 +- Emby.Photos/Emby.Photos.csproj | 2 +- Emby.Server.Implementations/Emby.Server.Implementations.csproj | 2 +- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- Jellyfin.Data/Jellyfin.Data.csproj | 2 +- Jellyfin.Networking/Jellyfin.Networking.csproj | 2 +- Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj | 2 +- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- MediaBrowser.Common/MediaBrowser.Common.csproj | 2 +- MediaBrowser.Controller/MediaBrowser.Controller.csproj | 2 +- MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj | 2 +- MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj | 2 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 2 +- MediaBrowser.Providers/MediaBrowser.Providers.csproj | 2 +- MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj | 2 +- src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj | 2 +- src/Jellyfin.Drawing/Jellyfin.Drawing.csproj | 2 +- src/Jellyfin.Extensions/Jellyfin.Extensions.csproj | 2 +- src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj | 2 +- .../Jellyfin.MediaEncoding.Keyframes.csproj | 2 +- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 2 +- tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj | 2 +- tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj | 2 +- tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj | 2 +- tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj | 2 +- .../Jellyfin.MediaEncoding.Hls.Tests.csproj | 2 +- .../Jellyfin.MediaEncoding.Keyframes.Tests.csproj | 2 +- tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj | 2 +- tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj | 2 +- tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj | 2 +- tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj | 2 +- tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj | 2 +- .../Jellyfin.Server.Implementations.Tests.csproj | 2 +- .../Jellyfin.Server.Integration.Tests.csproj | 2 +- tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj | 2 +- tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj | 2 +- 38 files changed, 38 insertions(+), 38 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index 7ffb7118aa..60e6dd644d 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -28,7 +28,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index 80bc57a5d6..3106e22465 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -47,7 +47,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/Emby.Notifications/Emby.Notifications.csproj b/Emby.Notifications/Emby.Notifications.csproj index 138965c89f..eb269183e9 100644 --- a/Emby.Notifications/Emby.Notifications.csproj +++ b/Emby.Notifications/Emby.Notifications.csproj @@ -23,7 +23,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj index 34bc8f32f6..ae6bc2db1f 100644 --- a/Emby.Photos/Emby.Photos.csproj +++ b/Emby.Photos/Emby.Photos.csproj @@ -26,7 +26,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 7accc3b8ba..7eaef094b5 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -54,7 +54,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index b5444138fb..45725ec3e6 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -27,7 +27,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index 7fe6466d4a..540534e1ba 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -29,7 +29,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/Jellyfin.Networking/Jellyfin.Networking.csproj b/Jellyfin.Networking/Jellyfin.Networking.csproj index 975d1c8ce2..2c153d88b5 100644 --- a/Jellyfin.Networking/Jellyfin.Networking.csproj +++ b/Jellyfin.Networking/Jellyfin.Networking.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index bc437c5d77..b078db0169 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -8,7 +8,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 829f294ce3..9ea8508f24 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -24,7 +24,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 0296974b54..1b0ff27d98 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -49,7 +49,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 6434621c46..20909c9d57 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -51,7 +51,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj index de3987b1e2..039127f9e3 100644 --- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj +++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj @@ -22,7 +22,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index e33cfc7a12..1233fb1108 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -31,7 +31,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 284e89f1cb..521ba0f107 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -49,7 +49,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 97ad1ffbcb..13de86a929 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -36,7 +36,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj index 6e82d96d10..c25932a5a1 100644 --- a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj +++ b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj @@ -22,7 +22,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj index c686b229a6..a62ebf78c7 100644 --- a/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj +++ b/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj @@ -31,7 +31,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj b/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj index a5bc8eaa7e..7aa9945033 100644 --- a/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj +++ b/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj @@ -23,7 +23,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj index 9fed8cbd9a..d7c05ea576 100644 --- a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj +++ b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj @@ -34,7 +34,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj b/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj index 32f80812ab..9a025d5586 100644 --- a/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj +++ b/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj @@ -7,7 +7,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj b/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj index b11bdc4779..fe4e576937 100644 --- a/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj +++ b/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index c0e0d2b6b3..6966d81d46 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -29,7 +29,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj index c74127f044..5110d59176 100644 --- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj +++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj @@ -24,7 +24,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj index 1ddf5139c9..97350fedad 100644 --- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj +++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj @@ -24,7 +24,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj index dc4b58fecf..a2ecd60838 100644 --- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj +++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj @@ -19,7 +19,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj index 16b18cc85e..313192b241 100644 --- a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj +++ b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj @@ -22,7 +22,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj b/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj index c20f3dd999..22b0c417b0 100644 --- a/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj @@ -21,7 +21,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj index 5cfad93a6a..373a54504e 100644 --- a/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj @@ -22,7 +22,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj index f824b6f3b2..a9a0dbc226 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj @@ -33,7 +33,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj index b6578a7f11..9858623f82 100644 --- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj +++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj @@ -26,7 +26,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj index f10f9159dd..920f490ed0 100644 --- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj +++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj @@ -28,7 +28,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj index 3a39daa364..74bf7cb0e0 100644 --- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj +++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj @@ -25,7 +25,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj index 6cc998d274..d3292c38eb 100644 --- a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj +++ b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj @@ -28,7 +28,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index 82628d7339..b796e07d1a 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -34,7 +34,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 006b38a11d..c40f6942b1 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -31,7 +31,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index 771fad6357..a72a6f1855 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -24,7 +24,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj index 0d69c3f611..dc5b5b9e6b 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj +++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj @@ -25,7 +25,7 @@ - + all runtime; build; native; contentfiles; analyzers -- cgit v1.2.3