diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 3f7a9454c5..9b44eff4c6 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
- "version": "10.0.6",
+ "version": "10.0.7",
"commands": [
"dotnet-ef"
]
diff --git a/.github/workflows/ci-codeql-analysis.yml b/.github/workflows/ci-codeql-analysis.yml
index 442114dd80..65752af977 100644
--- a/.github/workflows/ci-codeql-analysis.yml
+++ b/.github/workflows/ci-codeql-analysis.yml
@@ -32,13 +32,13 @@ jobs:
dotnet-version: '10.0.x'
- name: Initialize CodeQL
- uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
+ uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
- uses: github/codeql-action/autobuild@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
+ uses: github/codeql-action/autobuild@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
+ uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
index 9fdcaedf97..2a26bf15a4 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/ci-tests.yml
@@ -35,7 +35,7 @@ jobs:
--verbosity minimal
- name: Merge code coverage results
- uses: danielpalme/ReportGenerator-GitHub-Action@7ae927204961589fcb0b0be245c51fbbc87cbca2 # v5.5.5
+ uses: danielpalme/ReportGenerator-GitHub-Action@c31aa4ed4f12f147061186cf2a029f307b5c3636 # v5.5.9
with:
reports: "**/coverage.cobertura.xml"
targetdir: "merged/"
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 01f968690f..c42962786d 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -1,5 +1,6 @@
# Jellyfin Contributors
+ - [0x25CBFC4F](https://github.com/0x25CBFC4F)
- [1337joe](https://github.com/1337joe)
- [97carmine](https://github.com/97carmine)
- [Abbe98](https://github.com/Abbe98)
@@ -14,7 +15,7 @@
- [bilde2910](https://github.com/bilde2910)
- [bfayers](https://github.com/bfayers)
- [BnMcG](https://github.com/BnMcG)
- - [Bond-009](https://github.com/Bond-009)
+ - [Bond_009](https://github.com/Bond-009)
- [brianjmurrell](https://github.com/brianjmurrell)
- [bugfixin](https://github.com/bugfixin)
- [chaosinnovator](https://github.com/chaosinnovator)
@@ -31,6 +32,7 @@
- [DaveChild](https://github.com/DaveChild)
- [DavidFair](https://github.com/DavidFair)
- [Delgan](https://github.com/Delgan)
+ - [DerMaddis](https://github.com/dermaddis)
- [Derpipose](https://github.com/Derpipose)
- [dcrdev](https://github.com/dcrdev)
- [dhartung](https://github.com/dhartung)
@@ -54,6 +56,7 @@
- [geilername](https://github.com/geilername)
- [GermanCoding](https://github.com/GermanCoding)
- [gnattu](https://github.com/gnattu)
+ - [gnuyent](https://github.com/gnuyent)
- [GodTamIt](https://github.com/GodTamIt)
- [grafixeyehero](https://github.com/grafixeyehero)
- [h1nk](https://github.com/h1nk)
@@ -61,6 +64,7 @@
- [HelloWorld017](https://github.com/HelloWorld017)
- [ikomhoog](https://github.com/ikomhoog)
- [iwalton3](https://github.com/iwalton3)
+ - [Jakob Kukla](https://github.com/jakobkukla)
- [jftuga](https://github.com/jftuga)
- [jkhsjdhjs](https://github.com/jkhsjdhjs)
- [jmshrv](https://github.com/jmshrv)
@@ -69,8 +73,10 @@
- [JustAMan](https://github.com/JustAMan)
- [justinfenn](https://github.com/justinfenn)
- [JPVenson](https://github.com/JPVenson)
+ - [JPUC1143](https://github.com/Jpuc1143/)
- [KerryRJ](https://github.com/KerryRJ)
- [Larvitar](https://github.com/Larvitar)
+ - [lbenini](https://github.com/lbenini)
- [LeoVerto](https://github.com/LeoVerto)
- [Liggy](https://github.com/Liggy)
- [lmaonator](https://github.com/lmaonator)
@@ -83,15 +89,19 @@
- [marius-luca-87](https://github.com/marius-luca-87)
- [mark-monteiro](https://github.com/mark-monteiro)
- [MarkCiliaVincenti](https://github.com/MarkCiliaVincenti)
+ - [Martin Reuter](https://github.com/reuterma24)
- [Matt07211](https://github.com/Matt07211)
+ - [Matthew Jones](https://github.com/matthew-jones-uk)
- [Maxr1998](https://github.com/Maxr1998)
- [mcarlton00](https://github.com/mcarlton00)
+ - [Michael McElroy](https://github.com/mcmcelro)
- [mitchfizz05](https://github.com/mitchfizz05)
- [mohd-akram](https://github.com/mohd-akram)
- [MrTimscampi](https://github.com/MrTimscampi)
- [n8225](https://github.com/n8225)
- [Nalsai](https://github.com/Nalsai)
- [Narfinger](https://github.com/Narfinger)
+ - [Nathan McCrina](https://github.com/nfmccrina)
- [NathanPickard](https://github.com/NathanPickard)
- [neilsb](https://github.com/neilsb)
- [nevado](https://github.com/nevado)
@@ -102,6 +112,7 @@
- [OancaAndrei](https://github.com/OancaAndrei)
- [obradovichv](https://github.com/obradovichv)
- [oddstr13](https://github.com/oddstr13)
+ - [olsh](https://github.com/olsh)
- [orryverducci](https://github.com/orryverducci)
- [petermcneil](https://github.com/petermcneil)
- [Phlogi](https://github.com/Phlogi)
@@ -112,6 +123,7 @@
- [RazeLighter777](https://github.com/RazeLighter777)
- [redSpoutnik](https://github.com/redSpoutnik)
- [ringmatter](https://github.com/ringmatter)
+ - [Robert Lützner](https://github.com/rluetzner)
- [ryan-hartzell](https://github.com/ryan-hartzell)
- [s0urcelab](https://github.com/s0urcelab)
- [sachk](https://github.com/sachk)
@@ -127,6 +139,7 @@
- [sl1288](https://github.com/sl1288)
- [Smith00101010](https://github.com/Smith00101010)
- [sorinyo2004](https://github.com/sorinyo2004)
+ - [Soumyadip Auddy](https://github.com/SoumyadipAuddy)
- [sparky8251](https://github.com/sparky8251)
- [spookbits](https://github.com/spookbits)
- [ssenart](https://github.com/ssenart)
@@ -149,6 +162,7 @@
- [twinkybot](https://github.com/twinkybot)
- [Ullmie02](https://github.com/Ullmie02)
- [Unhelpful](https://github.com/Unhelpful)
+ - [Utku Özdemir](https://github.com/utkuozdemir)
- [viaregio](https://github.com/viaregio)
- [vitorsemeano](https://github.com/vitorsemeano)
- [voodoos](https://github.com/voodoos)
@@ -213,6 +227,8 @@
- [ZeusCraft10](https://github.com/ZeusCraft10)
- [MarcoCoreDuo](https://github.com/MarcoCoreDuo)
- [LiHRaM](https://github.com/LiHRaM)
+ - [MSalman5230](https://github.com/MSalman5230)
+ - [dwandw](https://github.com/dwandw)
# Emby Contributors
@@ -276,17 +292,3 @@
- [tikuf](https://github.com/tikuf/)
- [Tim Hobbs](https://github.com/timhobbs)
- [SvenVandenbrande](https://github.com/SvenVandenbrande)
- - [olsh](https://github.com/olsh)
- - [lbenini](https://github.com/lbenini)
- - [gnuyent](https://github.com/gnuyent)
- - [Matthew Jones](https://github.com/matthew-jones-uk)
- - [Jakob Kukla](https://github.com/jakobkukla)
- - [Utku Özdemir](https://github.com/utkuozdemir)
- - [JPUC1143](https://github.com/Jpuc1143/)
- - [0x25CBFC4F](https://github.com/0x25CBFC4F)
- - [Robert Lützner](https://github.com/rluetzner)
- - [Nathan McCrina](https://github.com/nfmccrina)
- - [Martin Reuter](https://github.com/reuterma24)
- - [Michael McElroy](https://github.com/mcmcelro)
- - [Soumyadip Auddy](https://github.com/SoumyadipAuddy)
- - [DerMaddis](https://github.com/dermaddis)
diff --git a/Directory.Packages.props b/Directory.Packages.props
index d2b715d6aa..9768d39a27 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -6,7 +6,7 @@
-
+
@@ -14,10 +14,10 @@
-
+
-
+
@@ -26,28 +26,28 @@
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -77,14 +77,13 @@
-
+
-
+
-
-
-
-
+
+
+
diff --git a/Emby.Server.Implementations/Chapters/ChapterManager.cs b/Emby.Server.Implementations/Chapters/ChapterManager.cs
index d09ed30ae3..79ab29b87c 100644
--- a/Emby.Server.Implementations/Chapters/ChapterManager.cs
+++ b/Emby.Server.Implementations/Chapters/ChapterManager.cs
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Chapters;
using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
@@ -232,12 +233,22 @@ public class ChapterManager : IChapterManager
}
///
- public void SaveChapters(Video video, IReadOnlyList chapters)
+ public bool Supports(BaseItem item)
+ => item is Video or Audio;
+
+ ///
+ public void SaveChapters(BaseItem item, IReadOnlyList chapters)
{
- // Remove any chapters that are outside of the runtime of the video
- var validChapters = chapters.Where(c => c.StartPositionTicks < video.RunTimeTicks).ToList();
- _chapterRepository.SaveChapters(video.Id, validChapters);
- }
+ if (!Supports(item))
+ {
+ _logger.LogWarning("Attempted to save chapters for unsupported item type {Type}: {Name} ({Id})", item.GetType().Name, item.Name, item.Id);
+ return;
+ }
+
+ // Remove any chapters that are outside of the runtime of the item
+ var validChapters = chapters.Where(c => c.StartPositionTicks < item.RunTimeTicks).ToList();
+ _chapterRepository.SaveChapters(item.Id, validChapters);
+}
///
public ChapterInfo? GetChapter(Guid baseItemId, int index)
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index 6a52dfe311..cc57d183b6 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -1224,11 +1224,6 @@ namespace Emby.Server.Implementations.Dto
}
}
- if (options.ContainsField(ItemFields.Chapters))
- {
- dto.Chapters = _chapterManager.GetChapters(item.Id).ToList();
- }
-
if (options.ContainsField(ItemFields.Trickplay))
{
var trickplay = _trickplayManager.GetTrickplayManifest(item).GetAwaiter().GetResult();
@@ -1242,6 +1237,11 @@ namespace Emby.Server.Implementations.Dto
dto.ExtraType = video.ExtraType;
}
+ if (options.ContainsField(ItemFields.Chapters))
+ {
+ dto.Chapters = _chapterManager.GetChapters(item.Id).ToList();
+ }
+
if (options.ContainsField(ItemFields.MediaStreams))
{
// Add VideoInfo
diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
index 3ee1c757f2..1e885aad6e 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
{
public class BookResolver : ItemResolver
{
- private readonly string[] _validExtensions = { ".azw", ".azw3", ".cb7", ".cbr", ".cbt", ".cbz", ".epub", ".mobi", ".pdf", ".m4b", ".m4a", ".aac", ".flac", ".mp3", ".opus" };
+ private readonly string[] _validExtensions = { ".azw", ".azw3", ".cb7", ".cbr", ".cbt", ".cbz", ".epub", ".mobi", ".pdf" };
protected override Book Resolve(ItemResolveArgs args)
{
diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json
index 7ce8baef59..49c5fe9180 100644
--- a/Emby.Server.Implementations/Localization/Core/ar.json
+++ b/Emby.Server.Implementations/Localization/Core/ar.json
@@ -1,141 +1,141 @@
{
- "Albums": "ألبومات",
- "AppDeviceValues": "تطبيق: {0}, جهاز: {1}",
- "Application": "تطبيق",
- "Artists": "فنانون",
- "AuthenticationSucceededWithUserName": "نجحت عملية التوثيق بـ {0}",
+ "Albums": "الألبومات",
+ "AppDeviceValues": "التطبيق: {0}، الجهاز: {1}",
+ "Application": "التطبيق",
+ "Artists": "الفنانون",
+ "AuthenticationSucceededWithUserName": "تمت مصادقة {0} بنجاح",
"Books": "الكتب",
- "CameraImageUploadedFrom": "رُفعت صورة الكاميرا الجديدة من {0}",
+ "CameraImageUploadedFrom": "تم رفع صورة كاميرا جديدة من {0}",
"Channels": "القنوات",
"ChapterNameValue": "الفصل {0}",
- "Collections": "مجموعات",
- "DeviceOfflineWithName": "قُطِع الاتصال ب{0}",
+ "Collections": "المجموعات",
+ "DeviceOfflineWithName": "انقطع اتصال {0}",
"DeviceOnlineWithName": "{0} متصل",
- "FailedLoginAttemptWithUserName": "محاولة تسجيل الدخول فاشلة من {0}",
+ "FailedLoginAttemptWithUserName": "محاولة تسجيل دخول فاشلة من {0}",
"Favorites": "المفضلة",
"Folders": "المجلدات",
- "Genres": "التصنيفات",
- "HeaderAlbumArtists": "فناني الألبوم",
+ "Genres": "الأنواع",
+ "HeaderAlbumArtists": "فنانو الألبوم",
"HeaderContinueWatching": "متابعة المشاهدة",
"HeaderFavoriteAlbums": "الألبومات المفضلة",
"HeaderFavoriteArtists": "الفنانون المفضلون",
"HeaderFavoriteEpisodes": "الحلقات المفضلة",
"HeaderFavoriteShows": "المسلسلات المفضلة",
"HeaderFavoriteSongs": "الأغاني المفضلة",
- "HeaderLiveTV": "التلفاز المباشر",
+ "HeaderLiveTV": "البث التلفزيوني المباشر",
"HeaderNextUp": "التالي",
"HeaderRecordingGroups": "مجموعات التسجيل",
- "HomeVideos": "الفيديوهات الشخصية",
- "Inherit": "توريث",
- "ItemAddedWithName": "أُضيف {0} للمكتبة",
- "ItemRemovedWithName": "أُزيل {0} من المكتبة",
- "LabelIpAddressValue": "عنوان الآي بي: {0}",
+ "HomeVideos": "فيديوهات منزلية",
+ "Inherit": "وراثة",
+ "ItemAddedWithName": "تمت إضافة {0} إلى المكتبة",
+ "ItemRemovedWithName": "تمت إزالة {0} من المكتبة",
+ "LabelIpAddressValue": "عنوان IP: {0}",
"LabelRunningTimeValue": "مدة التشغيل: {0}",
"Latest": "الأحدث",
- "MessageApplicationUpdated": "حُدث خادم Jellyfin",
- "MessageApplicationUpdatedTo": "حُدث خادم Jellyfin إلى {0}",
- "MessageNamedServerConfigurationUpdatedWithValue": "حُدثت إعدادات الخادم في قسم {0}",
- "MessageServerConfigurationUpdated": "حُدثت إعدادات الخادم",
+ "MessageApplicationUpdated": "تم تحديث خادم Jellyfin",
+ "MessageApplicationUpdatedTo": "تم تحديث خادم Jellyfin إلى {0}",
+ "MessageNamedServerConfigurationUpdatedWithValue": "تم تحديث قسم إعدادات الخادم {0}",
+ "MessageServerConfigurationUpdated": "تم تحديث إعدادات الخادم",
"MixedContent": "محتوى مختلط",
"Movies": "الأفلام",
"Music": "الموسيقى",
"MusicVideos": "الفيديوهات الموسيقية",
"NameInstallFailed": "فشل تثبيت {0}",
"NameSeasonNumber": "الموسم {0}",
- "NameSeasonUnknown": "الموسم غير معروف",
- "NewVersionIsAvailable": "نسخة جديدة من خادم Jellyfin متوفرة للتحميل.",
- "NotificationOptionApplicationUpdateAvailable": "يوجد تحديث للتطبيق",
- "NotificationOptionApplicationUpdateInstalled": "نُصب تحديث التطبيق",
- "NotificationOptionAudioPlayback": "بدأ تشغيل المقطع الصوتي",
- "NotificationOptionAudioPlaybackStopped": "أُوقف تشغيل المقطع الصوتي",
- "NotificationOptionCameraImageUploaded": "رُفعت صورة الكاميرا",
- "NotificationOptionInstallationFailed": "فشل في التثبيت",
- "NotificationOptionNewLibraryContent": "أُضيف محتوى جديدا",
- "NotificationOptionPluginError": "فشل في الملحق",
- "NotificationOptionPluginInstalled": "ثُبتت الملحق",
+ "NameSeasonUnknown": "موسم غير معروف",
+ "NewVersionIsAvailable": "يتوفر إصدار جديد من خادم Jellyfin للتنزيل.",
+ "NotificationOptionApplicationUpdateAvailable": "تحديث التطبيق متاح",
+ "NotificationOptionApplicationUpdateInstalled": "تم تثبيت تحديث التطبيق",
+ "NotificationOptionAudioPlayback": "بدأ تشغيل الصوت",
+ "NotificationOptionAudioPlaybackStopped": "توقف تشغيل الصوت",
+ "NotificationOptionCameraImageUploaded": "تم رفع صورة كاميرا",
+ "NotificationOptionInstallationFailed": "فشل التثبيت",
+ "NotificationOptionNewLibraryContent": "تمت إضافة محتوى جديد",
+ "NotificationOptionPluginError": "خطأ في الملحق",
+ "NotificationOptionPluginInstalled": "تم تثبيت الملحق",
"NotificationOptionPluginUninstalled": "تمت إزالة الملحق",
- "NotificationOptionPluginUpdateInstalled": "تم تثبيت تحديثات الملحق",
- "NotificationOptionServerRestartRequired": "يجب إعادة تشغيل الخادم",
- "NotificationOptionTaskFailed": "فشل في المهمة المجدولة",
- "NotificationOptionUserLockedOut": "تم إقفال حساب المستخدم",
+ "NotificationOptionPluginUpdateInstalled": "تم تحديث الملحق",
+ "NotificationOptionServerRestartRequired": "مطلوب إعادة تشغيل الخادم",
+ "NotificationOptionTaskFailed": "فشل المهمة المجدولة",
+ "NotificationOptionUserLockedOut": "تم قفل حساب المستخدم",
"NotificationOptionVideoPlayback": "بدأ تشغيل الفيديو",
- "NotificationOptionVideoPlaybackStopped": "تم إيقاف تشغيل الفيديو",
+ "NotificationOptionVideoPlaybackStopped": "توقف تشغيل الفيديو",
"Photos": "الصور",
"Playlists": "قوائم التشغيل",
"Plugin": "الملحق",
"PluginInstalledWithName": "تم تثبيت {0}",
"PluginUninstalledWithName": "تمت إزالة {0}",
"PluginUpdatedWithName": "تم تحديث {0}",
- "ProviderValue": "المزود: {0}",
- "ScheduledTaskFailedWithName": "فشلت العملية {0}",
- "ScheduledTaskStartedWithName": "تم بدء العملية {0}",
- "ServerNameNeedsToBeRestarted": "يحتاج {0} لإعادة التشغيل",
- "Shows": "العروض",
+ "ProviderValue": "المزوّد: {0}",
+ "ScheduledTaskFailedWithName": "فشلت {0}",
+ "ScheduledTaskStartedWithName": "بدأت {0}",
+ "ServerNameNeedsToBeRestarted": "يحتاج {0} إلى إعادة التشغيل",
+ "Shows": "المسلسلات",
"Songs": "الأغاني",
- "StartupEmbyServerIsLoading": "يتم تحميل خادم Jellyfin . الرجاء المحاولة بعد قليل.",
- "SubtitleDownloadFailureFromForItem": "فشل تحميل الترجمات من {0} ل {1}",
+ "StartupEmbyServerIsLoading": "يتم الآن تحميل خادم Jellyfin. يرجى المحاولة مرة أخرى بعد قليل.",
+ "SubtitleDownloadFailureFromForItem": "فشل تنزيل الترجمات من {0} لـ {1}",
"Sync": "مزامنة",
"System": "النظام",
"TvShows": "البرامج التلفزيونية",
"User": "المستخدم",
"UserCreatedWithName": "تم إنشاء المستخدم {0}",
"UserDeletedWithName": "تم حذف المستخدم {0}",
- "UserDownloadingItemWithValues": "يقوم {0} بتنزيل {1}",
- "UserLockedOutWithName": "تم منع المستخدم {0} من الدخول",
- "UserOfflineFromDevice": "تم قطع اتصال {0} من {1}",
- "UserOnlineFromDevice": "{0} متصل عبر {1}",
- "UserPasswordChangedWithName": "تم تغيير كلمة السر للمستخدم {0}",
- "UserPolicyUpdatedWithName": "تم تحديث سياسة المستخدم {0}",
- "UserStartedPlayingItemWithValues": "قام {0} ببدء تشغيل {1} على {2}",
- "UserStoppedPlayingItemWithValues": "قام {0} بإيقاف تشغيل {1} على {2}",
- "ValueHasBeenAddedToLibrary": "تمت اضافت {0} إلى مكتبة الوسائط",
- "ValueSpecialEpisodeName": "حلقة خاصه - {0}",
+ "UserDownloadingItemWithValues": "{0} يقوم بتنزيل {1}",
+ "UserLockedOutWithName": "تم قفل حساب المستخدم {0}",
+ "UserOfflineFromDevice": "انقطع اتصال {0} من {1}",
+ "UserOnlineFromDevice": "{0} متصل من {1}",
+ "UserPasswordChangedWithName": "تم تغيير كلمة المرور للمستخدم {0}",
+ "UserPolicyUpdatedWithName": "تم تحديث سياسة المستخدم لـ {0}",
+ "UserStartedPlayingItemWithValues": "{0} يقوم بتشغيل {1} على {2}",
+ "UserStoppedPlayingItemWithValues": "أنهى {0} تشغيل {1} على {2}",
+ "ValueHasBeenAddedToLibrary": "تمت إضافة {0} إلى مكتبة المحتوى الخاصة بك",
+ "ValueSpecialEpisodeName": "خاص - {0}",
"VersionNumber": "الإصدار {0}",
- "TaskCleanCacheDescription": "يحذف الملفات المؤقتة التي لم يعد النظام بحاجة إليها.",
- "TaskCleanCache": "حذف الملفات المؤقتة",
+ "TaskCleanCacheDescription": "يحذف ملفات ذاكرة التخزين المؤقت التي لم يعد النظام بحاجة إليها.",
+ "TaskCleanCache": "تنظيف مجلد ذاكرة التخزين المؤقت",
"TasksChannelsCategory": "قنوات الإنترنت",
- "TasksLibraryCategory": "مكتبة",
- "TasksMaintenanceCategory": "صيانة",
- "TaskRefreshLibraryDescription": "يفحص مكتبة الوسائط الخاصة بك باحثا عن ملفات جديدة، ومن ثم يُحدث البيانات الوصفية.",
- "TaskRefreshLibrary": "افحص مكتبة الوسائط",
- "TaskRefreshChapterImagesDescription": "يُنشئ صور مصغرة لمقاطع الفيديو التي تحتوي على فصول.",
- "TaskRefreshChapterImages": "استخراج صور الفصل",
- "TasksApplicationCategory": "تطبيق",
- "TaskDownloadMissingSubtitlesDescription": "يبحث في الإنترنت على الترجمات الناقصة استنادا على البيانات الوصفية.",
- "TaskDownloadMissingSubtitles": "تحميل الترجمات الناقصة",
- "TaskRefreshChannelsDescription": "يحدث معلومات قنوات الإنترنت.",
- "TaskRefreshChannels": "إعادة تحديث القنوات",
- "TaskCleanTranscodeDescription": "يحذف ملفات الترميز الأقدم من يوم واحد.",
- "TaskCleanTranscode": "حذف ما بمجلد الترميز",
- "TaskUpdatePluginsDescription": "تحميل وتثبيت الإضافات التي تم تفعيل التحديث التلقائي لها.",
- "TaskUpdatePlugins": "تحديث الإضافات",
- "TaskRefreshPeopleDescription": "يقوم بتحديث البيانات الوصفية للممثلين والمخرجين في مكتبة الوسائط الخاصة بك.",
- "TaskRefreshPeople": "إعادة تحميل الأشخاص",
- "TaskCleanLogsDescription": "يحذف السجلات الأقدم من {0} يوم.",
- "TaskCleanLogs": "حذف مسار السجل",
- "TaskCleanActivityLogDescription": "يحذف سجل الأنشطة الأقدم من الوقت الذي تم تحديده.",
- "TaskCleanActivityLog": "حذف سجل الأنشطة",
- "Default": "افتراضي",
- "Undefined": "غير معرف",
- "Forced": "ملحقة",
- "TaskOptimizeDatabaseDescription": "يضغط قاعدة البيانات ويقتطع المساحة الحرة. تشغيل هذه المهمة بعد فحص المكتبة أو إجراء تغييرات أخرى تتضمن تعديلات في قاعدة البيانات قد تؤدي إلى تحسين الأداء.",
+ "TasksLibraryCategory": "المكتبة",
+ "TasksMaintenanceCategory": "الصيانة",
+ "TaskRefreshLibraryDescription": "يفحص مكتبة المحتوى الخاصة بك بحثاً عن ملفات جديدة ويحدّث البيانات الوصفية.",
+ "TaskRefreshLibrary": "فحص مكتبة المحتوى",
+ "TaskRefreshChapterImagesDescription": "ينشئ صوراً مصغرة للفيديوهات التي تحتوي على فصول.",
+ "TaskRefreshChapterImages": "استخراج صور الفصول",
+ "TasksApplicationCategory": "التطبيق",
+ "TaskDownloadMissingSubtitlesDescription": "يبحث في الإنترنت عن الترجمات المفقودة بناءً على إعدادات البيانات الوصفية.",
+ "TaskDownloadMissingSubtitles": "تنزيل الترجمات المفقودة",
+ "TaskRefreshChannelsDescription": "يحدّث معلومات قنوات الإنترنت.",
+ "TaskRefreshChannels": "تحديث القنوات",
+ "TaskCleanTranscodeDescription": "يحذف ملفات تحويل الترميز التي مر عليها أكثر من يوم واحد.",
+ "TaskCleanTranscode": "تنظيف مجلد تحويل الترميز",
+ "TaskUpdatePluginsDescription": "ينزّل ويثبّت التحديثات للملحقات المهيأة للتحديث التلقائي.",
+ "TaskUpdatePlugins": "تحديث الملحقات",
+ "TaskRefreshPeopleDescription": "يحدّث البيانات الوصفية للممثلين والمخرجين في مكتبة المحتوى الخاصة بك.",
+ "TaskRefreshPeople": "تحديث الأشخاص",
+ "TaskCleanLogsDescription": "يحذف ملفات السجل التي يزيد عمرها عن {0} أيام.",
+ "TaskCleanLogs": "تنظيف مجلد السجلات",
+ "TaskCleanActivityLogDescription": "يحذف إدخالات سجل النشاط الأقدم من العمر المحدد.",
+ "TaskCleanActivityLog": "تنظيف سجل النشاط",
+ "Default": "الافتراضي",
+ "Undefined": "غير محدد",
+ "Forced": "إجباري",
+ "TaskOptimizeDatabaseDescription": "يضغط قاعدة البيانات ويقلل المساحة الحرة. قد يؤدي تشغيل هذه المهمة بعد فحص المكتبة أو إجراء تغييرات أخرى تتضمن تعديلات على قاعدة البيانات إلى تحسين الأداء.",
"TaskOptimizeDatabase": "تحسين قاعدة البيانات",
- "TaskKeyframeExtractorDescription": "يستخرج الإطارات الرئيسية من ملفات الفيديو لكي ينشئ قوائم تشغيل بث HTTP المباشر. قد تستمر هذه العملية لوقت طويل.",
- "TaskKeyframeExtractor": "مستخرج الإطار الرئيسي",
+ "TaskKeyframeExtractorDescription": "يستخرج الإطارات الرئيسية من ملفات الفيديو لإنشاء قوائم تشغيل HLS أكثر دقة. قد يستغرق تشغيل هذه المهمة وقتاً طويلاً.",
+ "TaskKeyframeExtractor": "مستخرج الإطارات الرئيسية",
"External": "خارجي",
- "HearingImpaired": "ضعاف السمع",
- "TaskRefreshTrickplayImages": "توليد صور المعاينة السريعة",
- "TaskRefreshTrickplayImagesDescription": "يُولّد معاينات تنقل سريع لمقاطع الفيديو ضمن المكتبات المفعّلة.",
- "TaskCleanCollectionsAndPlaylists": "حذف المجموعات وقوائم التشغيل",
- "TaskCleanCollectionsAndPlaylistsDescription": "حذف عناصر من المجموعات وقوائم التشغيل التي لم تعد موجودة.",
- "TaskAudioNormalization": "تسوية الصوت",
- "TaskAudioNormalizationDescription": "مسح الملفات لتطبيع بيانات الصوت.",
- "TaskDownloadMissingLyrics": "تنزيل عبارات القصيدة",
- "TaskDownloadMissingLyricsDescription": "كلمات",
- "TaskExtractMediaSegments": "فحص مقاطع الوسائط",
- "TaskExtractMediaSegmentsDescription": "يستخرج مقاطع وسائط من إضافات MediaSegment المُفعّلة.",
- "TaskMoveTrickplayImages": "تغيير مكان صور المعاينة السريعة",
- "TaskMoveTrickplayImagesDescription": "تُنقل ملفات التشغيل السريع الحالية بناءً على إعدادات المكتبة.",
+ "HearingImpaired": "لضعاف السمع",
+ "TaskRefreshTrickplayImages": "إنشاء صور معاينات التنقل (Trickplay)",
+ "TaskRefreshTrickplayImagesDescription": "ينشئ صور معاينات التنقل السريع للفيديوهات في المكتبات المفعّلة.",
+ "TaskCleanCollectionsAndPlaylists": "تنظيف المجموعات وقوائم التشغيل",
+ "TaskCleanCollectionsAndPlaylistsDescription": "يزيل العناصر التي لم تعد موجودة من المجموعات وقوائم التشغيل.",
+ "TaskAudioNormalization": "تطبيع الصوت",
+ "TaskAudioNormalizationDescription": "يفحص الملفات لجمع بيانات تطبيع الصوت.",
+ "TaskDownloadMissingLyrics": "تنزيل الكلمات المفقودة",
+ "TaskDownloadMissingLyricsDescription": "ينزّل الكلمات للأغاني.",
+ "TaskExtractMediaSegments": "فحص مقاطع المحتوى",
+ "TaskExtractMediaSegmentsDescription": "يستخرج أو يحصل على مقاطع المحتوى من الملحقات المفعّلة لمقاطع المحتوى (MediaSegment).",
+ "TaskMoveTrickplayImages": "نقل موقع صور معاينات التنقل",
+ "TaskMoveTrickplayImagesDescription": "ينقل ملفات معاينات التنقل الحالية وفقاً لإعدادات المكتبة.",
"CleanupUserDataTask": "مهمة تنظيف بيانات المستخدم",
- "CleanupUserDataTaskDescription": "مسح جميع بيانات المستخدم (حالة المشاهدة، والحالة المفضلة وما إلى ذلك) من الوسائط التي لم تعد موجودة لمدة 90 يومًا على الأقل."
+ "CleanupUserDataTaskDescription": "ينظف جميع بيانات المستخدم (مثل حالة المشاهدة وحالة المفضلة وغيرها) للمحتوى الذي لم يعد موجوداً لمدة 90 يوماً على الأقل."
}
diff --git a/Emby.Server.Implementations/Localization/Core/ar_SA.json b/Emby.Server.Implementations/Localization/Core/ar_SA.json
new file mode 100644
index 0000000000..0967ef424b
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/ar_SA.json
@@ -0,0 +1 @@
+{}
diff --git a/Emby.Server.Implementations/Localization/Core/ht.json b/Emby.Server.Implementations/Localization/Core/ht.json
index f927d3173a..183c422a85 100644
--- a/Emby.Server.Implementations/Localization/Core/ht.json
+++ b/Emby.Server.Implementations/Localization/Core/ht.json
@@ -58,5 +58,8 @@
"ValueSpecialEpisodeName": "Spesyal - {0}",
"VersionNumber": "Vesyon {0}",
"TasksApplicationCategory": "Aplikasyon",
- "TasksMaintenanceCategory": "Antretyen"
+ "TasksMaintenanceCategory": "Antretyen",
+ "AppDeviceValues": "Aplikasyon: {0}, Aparèy: {1}",
+ "AuthenticationSucceededWithUserName": "{0} otantifye avèk siksè",
+ "CameraImageUploadedFrom": "Une nouvelle image de la caméra a été téléchargée depuis {0}"
}
diff --git a/Emby.Server.Implementations/Localization/Core/mk.json b/Emby.Server.Implementations/Localization/Core/mk.json
index 6da31227d7..1895c7c8dc 100644
--- a/Emby.Server.Implementations/Localization/Core/mk.json
+++ b/Emby.Server.Implementations/Localization/Core/mk.json
@@ -132,5 +132,9 @@
"TaskAudioNormalization": "Нормализација на звукот",
"TaskRefreshTrickplayImagesDescription": "Креира трикплеј прегледи за видеа во овозможените библиотеки.",
"TaskCleanCollectionsAndPlaylistsDescription": "Отстранува ставки од колекциите и плејлистите што веќе не постојат.",
- "TaskExtractMediaSegments": "Скенирање на сегменти на содржина"
+ "TaskExtractMediaSegments": "Скенирање на сегменти на содржина",
+ "TaskMoveTrickplayImages": "Мигрирај ја локацијата на сликата од Trickplay",
+ "TaskMoveTrickplayImagesDescription": "Ги преместува постоечките датотеки за трикплеј според поставките на библиотеката.",
+ "CleanupUserDataTask": "Задача за чистење на кориснички податоци",
+ "CleanupUserDataTaskDescription": "Ги чисти сите кориснички податоци (состојба на гледање, статус на омилени итн.) од медиуми што повеќе не се присутни најмалку 90 дена."
}
diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json
index 76950467bd..de57c71acc 100644
--- a/Emby.Server.Implementations/Localization/Core/nl.json
+++ b/Emby.Server.Implementations/Localization/Core/nl.json
@@ -14,7 +14,7 @@
"Favorites": "Favorieten",
"Folders": "Mappen",
"HeaderAlbumArtists": "Albumartiesten",
- "HeaderContinueWatching": "Verder kijken",
+ "HeaderContinueWatching": "Verderkijken",
"HeaderFavoriteAlbums": "Favoriete albums",
"HeaderFavoriteArtists": "Favoriete artiesten",
"HeaderFavoriteEpisodes": "Favoriete afleveringen",
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index aa36430c7a..e2ddf86c7a 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -832,10 +832,6 @@ namespace Emby.Server.Implementations.Session
{
data.Played = true;
}
- else
- {
- data.Played = false;
- }
_userDataManager.SaveUserData(user, item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None);
}
diff --git a/Jellyfin.Api/Controllers/ActivityLogController.cs b/Jellyfin.Api/Controllers/ActivityLogController.cs
index 47d3f4b7f7..d6cc0e71a4 100644
--- a/Jellyfin.Api/Controllers/ActivityLogController.cs
+++ b/Jellyfin.Api/Controllers/ActivityLogController.cs
@@ -19,6 +19,7 @@ namespace Jellyfin.Api.Controllers;
///
[Route("System/ActivityLog")]
[Authorize(Policy = Policies.RequiresElevation)]
+[Tags("System")]
public class ActivityLogController : BaseJellyfinApiController
{
private readonly IActivityManager _activityManager;
diff --git a/Jellyfin.Api/Controllers/ApiKeyController.cs b/Jellyfin.Api/Controllers/ApiKeyController.cs
index 3363d7bad2..161479e4ca 100644
--- a/Jellyfin.Api/Controllers/ApiKeyController.cs
+++ b/Jellyfin.Api/Controllers/ApiKeyController.cs
@@ -14,6 +14,7 @@ namespace Jellyfin.Api.Controllers;
/// Authentication controller.
///
[Route("Auth")]
+[Tags("Authentication")]
public class ApiKeyController : BaseJellyfinApiController
{
private readonly IAuthenticationManager _authenticationManager;
diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs
index 99b0fde06d..f97ab414ce 100644
--- a/Jellyfin.Api/Controllers/ArtistsController.cs
+++ b/Jellyfin.Api/Controllers/ArtistsController.cs
@@ -25,6 +25,7 @@ namespace Jellyfin.Api.Controllers;
///
[Route("Artists")]
[Authorize]
+[Tags("Artist")]
public class ArtistsController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs
index 0d85b3a0db..e46ef0e31d 100644
--- a/Jellyfin.Api/Controllers/ChannelsController.cs
+++ b/Jellyfin.Api/Controllers/ChannelsController.cs
@@ -25,6 +25,7 @@ namespace Jellyfin.Api.Controllers;
/// Channels Controller.
///
[Authorize]
+[Tags("Channel")]
public class ChannelsController : BaseJellyfinApiController
{
private readonly IChannelManager _channelManager;
diff --git a/Jellyfin.Api/Controllers/ClientLogController.cs b/Jellyfin.Api/Controllers/ClientLogController.cs
index 139888bde8..c213b87940 100644
--- a/Jellyfin.Api/Controllers/ClientLogController.cs
+++ b/Jellyfin.Api/Controllers/ClientLogController.cs
@@ -15,6 +15,7 @@ namespace Jellyfin.Api.Controllers;
/// Client log controller.
///
[Authorize]
+[Tags("System")]
public class ClientLogController : BaseJellyfinApiController
{
private const int MaxDocumentSize = 1_000_000;
diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs
index 9e03fbeb06..ecd667b2e8 100644
--- a/Jellyfin.Api/Controllers/ConfigurationController.cs
+++ b/Jellyfin.Api/Controllers/ConfigurationController.cs
@@ -20,6 +20,7 @@ namespace Jellyfin.Api.Controllers;
///
[Route("System")]
[Authorize]
+[Tags("System")]
public class ConfigurationController : BaseJellyfinApiController
{
private readonly IServerConfigurationManager _configurationManager;
diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs
index 50050262f0..eadb8c9855 100644
--- a/Jellyfin.Api/Controllers/DevicesController.cs
+++ b/Jellyfin.Api/Controllers/DevicesController.cs
@@ -19,6 +19,7 @@ namespace Jellyfin.Api.Controllers;
/// Devices Controller.
///
[Authorize(Policy = Policies.RequiresElevation)]
+[Tags("Device")]
public class DevicesController : BaseJellyfinApiController
{
private readonly IDeviceManager _deviceManager;
diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
index ef54e9db54..c1287fe3f4 100644
--- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
+++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
@@ -20,6 +20,7 @@ namespace Jellyfin.Api.Controllers;
/// Display Preferences Controller.
///
[Authorize]
+[Tags("DisplayPreference")]
public class DisplayPreferencesController : BaseJellyfinApiController
{
private readonly IDisplayPreferencesManager _displayPreferencesManager;
@@ -156,13 +157,13 @@ public class DisplayPreferencesController : BaseJellyfinApiController
existingDisplayPreferences.SkipBackwardLength = displayPreferences.CustomPrefs.TryGetValue("skipBackLength", out var skipBackLength)
&& !string.IsNullOrEmpty(skipBackLength)
? int.Parse(skipBackLength, CultureInfo.InvariantCulture)
- : 10000;
+ : 15000;
displayPreferences.CustomPrefs.Remove("skipBackLength");
existingDisplayPreferences.SkipForwardLength = displayPreferences.CustomPrefs.TryGetValue("skipForwardLength", out var skipForwardLength)
&& !string.IsNullOrEmpty(skipForwardLength)
? int.Parse(skipForwardLength, CultureInfo.InvariantCulture)
- : 30000;
+ : 15000;
displayPreferences.CustomPrefs.Remove("skipForwardLength");
existingDisplayPreferences.DashboardTheme = displayPreferences.CustomPrefs.TryGetValue("dashboardTheme", out var theme)
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index c13da3ac7b..c059f5880d 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -38,6 +38,7 @@ namespace Jellyfin.Api.Controllers;
///
[Route("")]
[Authorize]
+[ApiExplorerSettings(IgnoreApi = true)]
public class DynamicHlsController : BaseJellyfinApiController
{
private const EncoderPreset DefaultVodEncoderPreset = EncoderPreset.veryfast;
diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs
index 456e643fd7..39c3f5abcf 100644
--- a/Jellyfin.Api/Controllers/GenresController.cs
+++ b/Jellyfin.Api/Controllers/GenresController.cs
@@ -25,6 +25,7 @@ namespace Jellyfin.Api.Controllers;
/// The genres controller.
///
[Authorize]
+[Tags("Genre")]
public class GenresController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs
index 1927a332b2..b5365cd632 100644
--- a/Jellyfin.Api/Controllers/HlsSegmentController.cs
+++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs
@@ -20,6 +20,7 @@ namespace Jellyfin.Api.Controllers;
/// The hls segment controller.
///
[Route("")]
+[ApiExplorerSettings(IgnoreApi = true)]
public class HlsSegmentController : BaseJellyfinApiController
{
private readonly IFileSystem _fileSystem;
diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs
index 301954561d..f80d32d149 100644
--- a/Jellyfin.Api/Controllers/InstantMixController.cs
+++ b/Jellyfin.Api/Controllers/InstantMixController.cs
@@ -320,6 +320,7 @@ public class InstantMixController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Obsolete("Use GetInstantMixFromArtists")]
+ [ApiExplorerSettings(IgnoreApi = true)]
public ActionResult> GetInstantMixFromArtists2(
[FromQuery, Required] Guid id,
[FromQuery] Guid? userId,
@@ -358,6 +359,7 @@ public class InstantMixController : BaseJellyfinApiController
[HttpGet("MusicGenres/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
+ [Obsolete("Use GetInstantMixFromMusicGenreByName")]
public ActionResult> GetInstantMixFromMusicGenreById(
[FromQuery, Required] Guid id,
[FromQuery] Guid? userId,
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index da52e7b23e..53656186c8 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -31,6 +31,7 @@ namespace Jellyfin.Api.Controllers;
///
[Route("")]
[Authorize]
+[Tags("Item")]
public class ItemsController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index cb0d449aa4..69c17f2486 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -113,20 +113,6 @@ public class LibraryController : BaseJellyfinApiController
return PhysicalFile(item.Path, MimeTypes.GetMimeType(item.Path), true);
}
- ///
- /// Gets critic review for an item.
- ///
- /// Critic reviews returned.
- /// The list of critic reviews.
- [HttpGet("Items/{itemId}/CriticReviews")]
- [Authorize]
- [Obsolete("This endpoint is obsolete.")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult> GetCriticReviews()
- {
- return new QueryResult();
- }
-
///
/// Get theme songs for an item.
///
@@ -971,7 +957,7 @@ public class LibraryController : BaseJellyfinApiController
CollectionType.playlists => new[] { "Playlist" },
CollectionType.movies => new[] { "Movie" },
CollectionType.tvshows => new[] { "Series", "Season", "Episode" },
- CollectionType.books => new[] { "Book" },
+ CollectionType.books => new[] { "Book", "AudioBook" },
CollectionType.music => new[] { "MusicArtist", "MusicAlbum", "Audio", "MusicVideo" },
CollectionType.homevideos => new[] { "Video", "Photo" },
CollectionType.photos => new[] { "Video", "Photo" },
diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index 9a32a303a9..2879b0fe53 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -344,6 +344,7 @@ public class LiveTvController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.LiveTvAccess)]
[Obsolete("This endpoint is obsolete.")]
+ [ApiExplorerSettings(IgnoreApi = true)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "channelId", Justification = "Imported from ServiceStack")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "groupId", Justification = "Imported from ServiceStack")]
@@ -387,6 +388,7 @@ public class LiveTvController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.LiveTvAccess)]
[Obsolete("This endpoint is obsolete.")]
+ [ApiExplorerSettings(IgnoreApi = true)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")]
public ActionResult> GetRecordingGroups([FromQuery] Guid? userId)
{
@@ -944,20 +946,6 @@ public class LiveTvController : BaseJellyfinApiController
return NoContent();
}
- ///
- /// Get recording group.
- ///
- /// Group id.
- /// A .
- [HttpGet("Recordings/Groups/{groupId}")]
- [Authorize(Policy = Policies.LiveTvAccess)]
- [ProducesResponseType(StatusCodes.Status404NotFound)]
- [Obsolete("This endpoint is obsolete.")]
- public ActionResult GetRecordingGroup([FromRoute, Required] Guid groupId)
- {
- return NotFound();
- }
-
///
/// Get guide info.
///
diff --git a/Jellyfin.Api/Controllers/LyricsController.cs b/Jellyfin.Api/Controllers/LyricsController.cs
index 8eb4cadf88..5a27b2719e 100644
--- a/Jellyfin.Api/Controllers/LyricsController.cs
+++ b/Jellyfin.Api/Controllers/LyricsController.cs
@@ -7,7 +7,6 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Extensions;
-using Jellyfin.Api.Helpers;
using Jellyfin.Extensions;
using MediaBrowser.Common.Api;
using MediaBrowser.Controller.Entities.Audio;
@@ -27,6 +26,7 @@ namespace Jellyfin.Api.Controllers;
/// Lyrics controller.
///
[Route("")]
+[Tags("Lyric")]
public class LyricsController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
diff --git a/Jellyfin.Api/Controllers/MediaSegmentsController.cs b/Jellyfin.Api/Controllers/MediaSegmentsController.cs
index b8836d7cf1..65565826a4 100644
--- a/Jellyfin.Api/Controllers/MediaSegmentsController.cs
+++ b/Jellyfin.Api/Controllers/MediaSegmentsController.cs
@@ -20,6 +20,7 @@ namespace Jellyfin.Api.Controllers;
/// Media Segments api.
///
[Authorize]
+[Tags("MediaSegment")]
public class MediaSegmentsController : BaseJellyfinApiController
{
private readonly IMediaSegmentManager _mediaSegmentManager;
diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs
index ace9a06395..50d34d0656 100644
--- a/Jellyfin.Api/Controllers/MoviesController.cs
+++ b/Jellyfin.Api/Controllers/MoviesController.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
@@ -18,6 +17,7 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Api.Controllers;
@@ -26,6 +26,7 @@ namespace Jellyfin.Api.Controllers;
/// Movies controller.
///
[Authorize]
+[Tags("Movie")]
public class MoviesController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs
index a6427df67a..7af44f8bd6 100644
--- a/Jellyfin.Api/Controllers/MusicGenresController.cs
+++ b/Jellyfin.Api/Controllers/MusicGenresController.cs
@@ -25,6 +25,7 @@ namespace Jellyfin.Api.Controllers;
/// The music genres controller.
///
[Authorize]
+[Tags("MusicGenre")]
public class MusicGenresController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
@@ -72,6 +73,7 @@ public class MusicGenresController : BaseJellyfinApiController
/// An containing the queryresult of music genres.
[HttpGet]
[Obsolete("Use GetGenres instead")]
+ [ApiExplorerSettings(IgnoreApi = true)]
public ActionResult> GetMusicGenres(
[FromQuery] int? startIndex,
[FromQuery] int? limit,
@@ -144,6 +146,7 @@ public class MusicGenresController : BaseJellyfinApiController
/// An containing a with the music genre.
[HttpGet("{genreName}")]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [Obsolete("Use GetGenre instead")]
public ActionResult GetMusicGenre([FromRoute, Required] string genreName, [FromQuery] Guid? userId)
{
userId = RequestHelpers.GetUserId(User, userId);
diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs
index 274e94ee6d..1f8f963f70 100644
--- a/Jellyfin.Api/Controllers/PackageController.cs
+++ b/Jellyfin.Api/Controllers/PackageController.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
-using Jellyfin.Api.Constants;
using MediaBrowser.Common.Api;
using MediaBrowser.Common.Updates;
using MediaBrowser.Controller.Configuration;
@@ -19,6 +18,7 @@ namespace Jellyfin.Api.Controllers;
///
[Route("")]
[Authorize(Policy = Policies.RequiresElevation)]
+[Tags("Plugin")]
public class PackageController : BaseJellyfinApiController
{
private readonly IInstallationManager _installationManager;
diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs
index 1811a219ac..8e7026341d 100644
--- a/Jellyfin.Api/Controllers/PersonsController.cs
+++ b/Jellyfin.Api/Controllers/PersonsController.cs
@@ -22,6 +22,7 @@ namespace Jellyfin.Api.Controllers;
/// Persons controller.
///
[Authorize]
+[Tags("Person")]
public class PersonsController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs
index 9679180937..048a49ffd4 100644
--- a/Jellyfin.Api/Controllers/PlaylistsController.cs
+++ b/Jellyfin.Api/Controllers/PlaylistsController.cs
@@ -29,6 +29,7 @@ namespace Jellyfin.Api.Controllers;
/// Playlists controller.
///
[Authorize]
+[Tags("Playlist")]
public class PlaylistsController : BaseJellyfinApiController
{
private readonly IPlaylistManager _playlistManager;
diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs
index ade0906b34..aa22bdf6af 100644
--- a/Jellyfin.Api/Controllers/PlaystateController.cs
+++ b/Jellyfin.Api/Controllers/PlaystateController.cs
@@ -6,7 +6,6 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Database.Implementations.Entities;
-using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
@@ -25,6 +24,7 @@ namespace Jellyfin.Api.Controllers;
///
[Route("")]
[Authorize]
+[Tags("Session")]
public class PlaystateController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
@@ -273,6 +273,7 @@ public class PlaystateController : BaseJellyfinApiController
[HttpPost("PlayingItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[Obsolete("This endpoint is obsolete. Use ReportPlaybackStart instead")]
+ [ApiExplorerSettings(IgnoreApi = true)]
public async Task OnPlaybackStart(
[FromRoute, Required] Guid itemId,
[FromQuery] string? mediaSourceId,
@@ -352,6 +353,7 @@ public class PlaystateController : BaseJellyfinApiController
[HttpPost("PlayingItems/{itemId}/Progress")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[Obsolete("This endpoint is obsolete. Use ReportPlaybackProgress instead")]
+ [ApiExplorerSettings(IgnoreApi = true)]
public async Task OnPlaybackProgress(
[FromRoute, Required] Guid itemId,
[FromQuery] string? mediaSourceId,
@@ -441,6 +443,7 @@ public class PlaystateController : BaseJellyfinApiController
[HttpDelete("PlayingItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[Obsolete("This endpoint is obsolete. Use ReportPlaybackStop instead")]
+ [ApiExplorerSettings(IgnoreApi = true)]
public async Task OnPlaybackStopped(
[FromRoute, Required] Guid itemId,
[FromQuery] string? mediaSourceId,
diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs
index 53b7349e7d..79e6536fb6 100644
--- a/Jellyfin.Api/Controllers/PluginsController.cs
+++ b/Jellyfin.Api/Controllers/PluginsController.cs
@@ -6,7 +6,6 @@ using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
-using Jellyfin.Api.Constants;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Api;
using MediaBrowser.Common.Plugins;
@@ -23,6 +22,7 @@ namespace Jellyfin.Api.Controllers;
/// Plugins controller.
///
[Authorize(Policy = Policies.RequiresElevation)]
+[Tags("Plugin")]
public class PluginsController : BaseJellyfinApiController
{
private readonly IInstallationManager _installationManager;
@@ -136,7 +136,6 @@ public class PluginsController : BaseJellyfinApiController
[HttpDelete("{pluginId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- [Obsolete("Please use the UninstallPluginByVersion API.")]
public ActionResult UninstallPlugin([FromRoute, Required] Guid pluginId)
{
// If no version is given, return the current instance.
diff --git a/Jellyfin.Api/Controllers/QuickConnectController.cs b/Jellyfin.Api/Controllers/QuickConnectController.cs
index bdb2a4d20b..5c7b38e137 100644
--- a/Jellyfin.Api/Controllers/QuickConnectController.cs
+++ b/Jellyfin.Api/Controllers/QuickConnectController.cs
@@ -16,6 +16,7 @@ namespace Jellyfin.Api.Controllers;
///
/// Quick connect controller.
///
+[Tags("Authentication")]
public class QuickConnectController : BaseJellyfinApiController
{
private readonly IQuickConnect _quickConnect;
diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs
index 065466cbca..f122d0f5e5 100644
--- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs
+++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
-using Jellyfin.Api.Constants;
using MediaBrowser.Common.Api;
using MediaBrowser.Model.Tasks;
using Microsoft.AspNetCore.Authorization;
@@ -15,6 +14,7 @@ namespace Jellyfin.Api.Controllers;
/// Scheduled Tasks Controller.
///
[Authorize(Policy = Policies.RequiresElevation)]
+[Tags("ScheduledTask")]
public class ScheduledTasksController : BaseJellyfinApiController
{
private readonly ITaskManager _taskManager;
diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs
index ad08dc5f9b..a8feb206a4 100644
--- a/Jellyfin.Api/Controllers/StudiosController.cs
+++ b/Jellyfin.Api/Controllers/StudiosController.cs
@@ -22,6 +22,7 @@ namespace Jellyfin.Api.Controllers;
/// Studios controller.
///
[Authorize]
+[Tags("Studio")]
public class StudiosController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs
index e9e404076f..9c5515dd92 100644
--- a/Jellyfin.Api/Controllers/SuggestionsController.cs
+++ b/Jellyfin.Api/Controllers/SuggestionsController.cs
@@ -23,6 +23,7 @@ namespace Jellyfin.Api.Controllers;
///
[Route("")]
[Authorize]
+[Tags("Suggestion")]
public class SuggestionsController : BaseJellyfinApiController
{
private readonly IDtoService _dtoService;
diff --git a/Jellyfin.Api/Controllers/TimeSyncController.cs b/Jellyfin.Api/Controllers/TimeSyncController.cs
index d7304cf426..fe6e11f9e2 100644
--- a/Jellyfin.Api/Controllers/TimeSyncController.cs
+++ b/Jellyfin.Api/Controllers/TimeSyncController.cs
@@ -9,6 +9,7 @@ namespace Jellyfin.Api.Controllers;
/// The time sync controller.
///
[Route("")]
+[Tags("System")]
public class TimeSyncController : BaseJellyfinApiController
{
///
diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs
index 99ff3a21ee..e2075c2b8d 100644
--- a/Jellyfin.Api/Controllers/TrailersController.cs
+++ b/Jellyfin.Api/Controllers/TrailersController.cs
@@ -16,6 +16,7 @@ namespace Jellyfin.Api.Controllers;
/// The trailers controller.
///
[Authorize]
+[Tags("Trailer")]
public class TrailersController : BaseJellyfinApiController
{
private readonly ItemsController _itemsController;
diff --git a/Jellyfin.Api/Controllers/TrickplayController.cs b/Jellyfin.Api/Controllers/TrickplayController.cs
index c9f8b36768..d7a10ce5f6 100644
--- a/Jellyfin.Api/Controllers/TrickplayController.cs
+++ b/Jellyfin.Api/Controllers/TrickplayController.cs
@@ -5,7 +5,6 @@ using System.Text;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Extensions;
-using Jellyfin.Api.Helpers;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Trickplay;
@@ -21,6 +20,7 @@ namespace Jellyfin.Api.Controllers;
///
[Route("")]
[Authorize]
+[Tags("TrickPlay")]
public class TrickplayController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs
index c86c9b8f61..e45a100b77 100644
--- a/Jellyfin.Api/Controllers/TvShowsController.cs
+++ b/Jellyfin.Api/Controllers/TvShowsController.cs
@@ -27,6 +27,7 @@ namespace Jellyfin.Api.Controllers;
///
[Route("Shows")]
[Authorize]
+[Tags("Show")]
public class TvShowsController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs
index f4e0c86143..d4e9b234c5 100644
--- a/Jellyfin.Api/Controllers/UniversalAudioController.cs
+++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs
@@ -29,6 +29,7 @@ namespace Jellyfin.Api.Controllers;
/// The universal audio controller.
///
[Route("")]
+[Tags("Audio")]
public class UniversalAudioController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
diff --git a/Jellyfin.Api/Controllers/UserViewsController.cs b/Jellyfin.Api/Controllers/UserViewsController.cs
index ed4bba2bb1..c1d06bad36 100644
--- a/Jellyfin.Api/Controllers/UserViewsController.cs
+++ b/Jellyfin.Api/Controllers/UserViewsController.cs
@@ -26,6 +26,7 @@ namespace Jellyfin.Api.Controllers;
///
[Route("")]
[Authorize]
+[Tags("UserView")]
public class UserViewsController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
diff --git a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs
index b67c6fdb7b..2c8b452c35 100644
--- a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs
+++ b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs
@@ -19,6 +19,7 @@ namespace Jellyfin.Api.Controllers;
/// Attachments controller.
///
[Route("Videos")]
+[Tags("Video")]
public class VideoAttachmentsController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index 6f6ff3ee41..2c2cbf1ec6 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -35,6 +35,7 @@ namespace Jellyfin.Api.Controllers;
///
/// The videos controller.
///
+[Tags("Video")]
public class VideosController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs
index 685334a9f0..aa6464ee7a 100644
--- a/Jellyfin.Api/Controllers/YearsController.cs
+++ b/Jellyfin.Api/Controllers/YearsController.cs
@@ -26,6 +26,7 @@ namespace Jellyfin.Api.Controllers;
/// Years controller.
///
[Authorize]
+[Tags("Year")]
public class YearsController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
diff --git a/Jellyfin.Data/Enums/PersonKind.cs b/Jellyfin.Data/Enums/PersonKind.cs
index 29308789a0..54eac5ff3b 100644
--- a/Jellyfin.Data/Enums/PersonKind.cs
+++ b/Jellyfin.Data/Enums/PersonKind.cs
@@ -129,5 +129,10 @@ public enum PersonKind
///
/// A person who renders a text from one language into another.
///
- Translator
+ Translator,
+
+ ///
+ /// A person who narrates a book or other work.
+ ///
+ Narrator
}
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index c71c193e2e..a498901481 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -312,7 +312,7 @@ namespace Jellyfin.Server.Extensions
return;
}
- if (prefixLength == NetworkConstants.MinimumIPv4PrefixSize)
+ if ((addr.AddressFamily == AddressFamily.InterNetwork && prefixLength == NetworkConstants.MinimumIPv4PrefixSize) || (addr.AddressFamily == AddressFamily.InterNetworkV6 && prefixLength == NetworkConstants.MinimumIPv6PrefixSize))
{
options.KnownProxies.Add(addr);
}
diff --git a/MediaBrowser.Controller/Chapters/IChapterManager.cs b/MediaBrowser.Controller/Chapters/IChapterManager.cs
index 25656fd625..edc20205aa 100644
--- a/MediaBrowser.Controller/Chapters/IChapterManager.cs
+++ b/MediaBrowser.Controller/Chapters/IChapterManager.cs
@@ -13,12 +13,19 @@ namespace MediaBrowser.Controller.Chapters;
///
public interface IChapterManager
{
+ ///
+ /// Gets a value indicating whether the specified item type is supported for chapter operations.
+ ///
+ /// The item to check.
+ /// true if the item type supports chapters; otherwise, false.
+ bool Supports(BaseItem item);
+
///
/// Saves the chapters.
///
- /// The video.
+ /// The item.
/// The set of chapters.
- void SaveChapters(Video video, IReadOnlyList chapters);
+ void SaveChapters(BaseItem item, IReadOnlyList chapters);
///
/// Gets a single chapter of a BaseItem on a specific index.
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 9f7e35d1ea..a0e04eae63 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -420,7 +420,9 @@ namespace MediaBrowser.Controller.MediaEncoding
}
return state.VideoStream.VideoRange == VideoRange.HDR
- && IsDoviWithHdr10Bl(state.VideoStream);
+ && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
+ || IsHdr10Plus(state.VideoStream)
+ || IsDoviWithHdr10Bl(state.VideoStream));
}
private bool IsVideoToolboxTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
@@ -435,8 +437,10 @@ namespace MediaBrowser.Controller.MediaEncoding
// Certain DV profile 5 video works in Safari with direct playing, but the VideoToolBox does not produce correct mapping results with transcoding.
// All other HDR formats working.
return state.VideoStream.VideoRange == VideoRange.HDR
- && (IsDoviWithHdr10Bl(state.VideoStream)
- || state.VideoStream.VideoRangeType is VideoRangeType.HLG);
+ && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
+ || IsHdr10Plus(state.VideoStream)
+ || IsDoviWithHdr10Bl(state.VideoStream)
+ || state.VideoStream.VideoRangeType == VideoRangeType.HLG);
}
private bool IsVideoStreamHevcRext(EncodingJobInfo state)
@@ -1617,13 +1621,25 @@ namespace MediaBrowser.Controller.MediaEncoding
mbbrcOpt = " -mbbrc 1";
}
+ // Some less powerful H.264 HW decoders require strict CPB size
+ // So bufsize optimizations should not be applied to them
+ int factor = 2;
+ var codec = state.ActualOutputVideoCodec;
+ var level = state.GetRequestedLevel(codec);
+ if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase)
+ && double.TryParse(level, CultureInfo.InvariantCulture, out double requestedLevel)
+ && requestedLevel < 51)
+ {
+ factor = 1;
+ }
+
// Set (maxrate == bitrate + 1) to trigger VBR for better bitrate allocation
// Set (rc_init_occupancy == 2 * bitrate) and (bufsize == 4 * bitrate) to deal with drastic scene changes
// Use long arithmetic and clamp to int.MaxValue to prevent int32 overflow
// (e.g. bitrate * 4 wraps to a negative value for bitrates above ~537 million)
int qsvMaxrate = (int)Math.Min((long)bitrate + 1, int.MaxValue);
- int qsvInitOcc = (int)Math.Min((long)bitrate * 2, int.MaxValue);
- int qsvBufsize = (int)Math.Min((long)bitrate * 4, int.MaxValue);
+ int qsvInitOcc = (int)Math.Min((long)bitrate * 1 * factor, int.MaxValue);
+ int qsvBufsize = (int)Math.Min((long)bitrate * 2 * factor, int.MaxValue);
return FormattableString.Invariant($"{mbbrcOpt} -b:v {bitrate} -maxrate {qsvMaxrate} -rc_init_occupancy {qsvInitOcc} -bufsize {qsvBufsize}");
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 770965cab3..f34e911a05 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -414,7 +414,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
///
public Task GetMediaInfo(MediaInfoRequest request, CancellationToken cancellationToken)
{
- var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters;
+ var extractChapters = request.ExtractChapters;
var extraArgs = GetExtraArguments(request);
return GetMediaInfoInternal(
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 3c6a03713f..a4d17e4f9d 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -194,6 +194,11 @@ namespace MediaBrowser.MediaEncoding.Probing
info.ProductionYear = info.PremiereDate.Value.Year;
}
+ if (data.Chapters is not null)
+ {
+ info.Chapters = data.Chapters.Select(GetChapterInfo).ToArray();
+ }
+
// Set mediaType-specific metadata
if (isAudio)
{
@@ -238,11 +243,6 @@ namespace MediaBrowser.MediaEncoding.Probing
FetchWtvInfo(info, data);
- if (data.Chapters is not null)
- {
- info.Chapters = data.Chapters.Select(GetChapterInfo).ToArray();
- }
-
ExtractTimestamp(info);
if (tags.TryGetValue("stereo_mode", out var stereoMode) && string.Equals(stereoMode, "left_right", StringComparison.OrdinalIgnoreCase))
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index 5920fe3289..894d0a3574 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -147,7 +147,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles
// Return the original if the same format is being requested
// Character encoding was already handled in GetSubtitleStream
- if (string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase))
+ // ASS is a superset of SSA, skipping the conversion and preserving the styles
+ if (string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase)
+ || (string.Equals(inputFormat, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase)
+ && string.Equals(outputFormat, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase)))
{
return stream;
}
diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs
index 79f8675cbf..c0d1bc86e7 100644
--- a/MediaBrowser.Model/Net/MimeTypes.cs
+++ b/MediaBrowser.Model/Net/MimeTypes.cs
@@ -132,6 +132,7 @@ namespace MediaBrowser.Model.Net
// Type image
new("image/jpeg", ".jpg"),
+ new("image/jpg", ".jpg"),
new("image/tiff", ".tiff"),
new("image/x-png", ".png"),
new("image/x-icon", ".ico"),
diff --git a/MediaBrowser.Providers/Books/OpenPackagingFormat/OpfReader.cs b/MediaBrowser.Providers/Books/OpenPackagingFormat/OpfReader.cs
index 5d202c59e1..15ea2ce5ab 100644
--- a/MediaBrowser.Providers/Books/OpenPackagingFormat/OpfReader.cs
+++ b/MediaBrowser.Providers/Books/OpenPackagingFormat/OpfReader.cs
@@ -260,6 +260,8 @@ namespace MediaBrowser.Providers.Books.OpenPackagingFormat
return PersonKind.Lyricist;
case "mus":
return PersonKind.AlbumArtist;
+ case "nrt":
+ return PersonKind.Narrator;
case "oth":
return PersonKind.Unknown;
case "trl":
diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
index e0354dbdfa..727f481b65 100644
--- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs
+++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
@@ -255,7 +255,7 @@ namespace MediaBrowser.Providers.Manager
catch (Exception ex)
{
result.ErrorMessage = ex.Message;
- _logger.LogError(ex, "Error in {Provider}", provider.Name);
+ _logger.LogError(ex, "Error in {Provider} for {Item}", provider.Name, item.Path ?? item.Name);
}
}
@@ -339,7 +339,7 @@ namespace MediaBrowser.Providers.Manager
catch (Exception ex)
{
result.ErrorMessage = ex.Message;
- _logger.LogError(ex, "Error in {Provider}", provider.Name);
+ _logger.LogError(ex, "Error in {Provider} for {Item}", provider.Name, item.Path ?? item.Name);
}
}
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index e9cb46eab5..abdfb1e3b7 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -820,7 +820,7 @@ namespace MediaBrowser.Providers.Manager
}
catch (Exception ex)
{
- Logger.LogError(ex, "Error in {Provider}", provider.Name);
+ Logger.LogError(ex, "Error in {Provider} for {Item}", provider.Name, logName);
// If a local provider fails, consider that a failure
refreshResult.ErrorMessage = ex.Message;
@@ -886,7 +886,7 @@ namespace MediaBrowser.Providers.Manager
catch (Exception ex)
{
refreshResult.ErrorMessage = ex.Message;
- Logger.LogError(ex, "Error in {Provider}", provider.Name);
+ Logger.LogError(ex, "Error in {Provider} for {Item}", provider.Name, logName);
}
}
@@ -935,7 +935,7 @@ namespace MediaBrowser.Providers.Manager
{
refreshResult.Failures++;
refreshResult.ErrorMessage = ex.Message;
- Logger.LogError(ex, "Error in {Provider}", provider.Name);
+ Logger.LogError(ex, "Error in {Provider} for {Item}", provider.Name, logName);
}
}
diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
index 869e3f292e..0ecbb6f068 100644
--- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
+++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
using ATL;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
+using MediaBrowser.Controller.Chapters;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
@@ -38,6 +39,7 @@ namespace MediaBrowser.Providers.MediaInfo
private readonly LyricResolver _lyricResolver;
private readonly ILyricManager _lyricManager;
private readonly IMediaStreamRepository _mediaStreamRepository;
+ private readonly IChapterManager _chapterManager;
///
/// Initializes a new instance of the class.
@@ -49,6 +51,7 @@ namespace MediaBrowser.Providers.MediaInfo
/// Instance of the interface.
/// Instance of the interface.
/// Instance of the .
+ /// Instance of the interface.
public AudioFileProber(
ILogger logger,
IMediaSourceManager mediaSourceManager,
@@ -56,7 +59,8 @@ namespace MediaBrowser.Providers.MediaInfo
ILibraryManager libraryManager,
LyricResolver lyricResolver,
ILyricManager lyricManager,
- IMediaStreamRepository mediaStreamRepository)
+ IMediaStreamRepository mediaStreamRepository,
+ IChapterManager chapterManager)
{
_mediaEncoder = mediaEncoder;
_libraryManager = libraryManager;
@@ -65,6 +69,7 @@ namespace MediaBrowser.Providers.MediaInfo
_lyricResolver = lyricResolver;
_lyricManager = lyricManager;
_mediaStreamRepository = mediaStreamRepository;
+ _chapterManager = chapterManager;
ATL.Settings.DisplayValueSeparator = InternalValueSeparator;
ATL.Settings.UseFileNameWhenNoTitle = false;
ATL.Settings.ID3v2_separatev2v3Values = false;
@@ -99,6 +104,7 @@ namespace MediaBrowser.Providers.MediaInfo
new MediaInfoRequest
{
MediaType = DlnaProfileType.Audio,
+ ExtractChapters = item is AudioBook,
MediaSource = new MediaSourceInfo
{
Path = path,
@@ -151,6 +157,11 @@ namespace MediaBrowser.Providers.MediaInfo
audio.HasLyrics = mediaStreams.Any(s => s.Type == MediaStreamType.Lyric);
_mediaStreamRepository.SaveMediaStreams(audio.Id, mediaStreams, cancellationToken);
+
+ if (audio is AudioBook && mediaInfo.Chapters is { Length: > 0 })
+ {
+ _chapterManager.SaveChapters(audio, mediaInfo.Chapters);
+ }
}
///
@@ -212,18 +223,6 @@ namespace MediaBrowser.Providers.MediaInfo
albumArtists = albumArtists.SelectMany(a => SplitWithCustomDelimiter(a, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist)).ToArray();
}
- foreach (var albumArtist in albumArtists)
- {
- if (!string.IsNullOrWhiteSpace(albumArtist))
- {
- PeopleHelper.AddPerson(people, new PersonInfo
- {
- Name = albumArtist,
- Type = PersonKind.AlbumArtist
- });
- }
- }
-
string[]? performers = null;
if (libraryOptions.PreferNonstandardArtistsTag)
{
@@ -244,32 +243,100 @@ namespace MediaBrowser.Providers.MediaInfo
performers = performers.SelectMany(p => SplitWithCustomDelimiter(p, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist)).ToArray();
}
- foreach (var performer in performers)
- {
- if (!string.IsNullOrWhiteSpace(performer))
- {
- PeopleHelper.AddPerson(people, new PersonInfo
- {
- Name = performer,
- Type = PersonKind.Artist
- });
- }
- }
+ var isAudioBook = audio is AudioBook;
- if (!string.IsNullOrWhiteSpace(trackComposer))
+ if (isAudioBook)
{
- foreach (var composer in trackComposer.Split(InternalValueSeparator))
+ // For audiobooks: AlbumArtists/Performers = Author, NARRATOR tag = Narrator,
+ // ILLUSTRATOR tag = Illustrator, Composer = fallback Narrator, other performers = Cast.
+ // If album_artist is missing, fall back to artist/performers for the author role.
+ var authorSource = albumArtists.Length > 0 ? albumArtists : performers;
+ var authorNames = new HashSet(StringComparer.OrdinalIgnoreCase);
+
+ foreach (var author in authorSource)
{
- if (!string.IsNullOrWhiteSpace(composer))
+ if (!string.IsNullOrWhiteSpace(author))
+ {
+ authorNames.Add(author.Trim());
+ PeopleHelper.AddPerson(people, new PersonInfo
+ {
+ Name = author.Trim(),
+ Type = PersonKind.Author
+ });
+ }
+ }
+
+ // Composer tag = Narrator (Audiobookshelf and other tools use Composer for narrator)
+ if (!string.IsNullOrWhiteSpace(trackComposer))
+ {
+ foreach (var composer in trackComposer.Split(InternalValueSeparator))
+ {
+ if (!string.IsNullOrWhiteSpace(composer))
+ {
+ PeopleHelper.AddPerson(people, new PersonInfo
+ {
+ Name = composer.Trim(),
+ Type = PersonKind.Narrator
+ });
+ }
+ }
+ }
+
+ // Any performers not already listed as authors get added as cast
+ foreach (var performer in performers)
+ {
+ if (!string.IsNullOrWhiteSpace(performer) && !authorNames.Contains(performer.Trim()))
{
PeopleHelper.AddPerson(people, new PersonInfo
{
- Name = composer,
- Type = PersonKind.Composer
+ Name = performer.Trim(),
+ Type = PersonKind.Actor
});
}
}
}
+ else
+ {
+ // Standard music track handling
+ foreach (var albumArtist in albumArtists)
+ {
+ if (!string.IsNullOrWhiteSpace(albumArtist))
+ {
+ PeopleHelper.AddPerson(people, new PersonInfo
+ {
+ Name = albumArtist,
+ Type = PersonKind.AlbumArtist
+ });
+ }
+ }
+
+ foreach (var performer in performers)
+ {
+ if (!string.IsNullOrWhiteSpace(performer))
+ {
+ PeopleHelper.AddPerson(people, new PersonInfo
+ {
+ Name = performer,
+ Type = PersonKind.Artist
+ });
+ }
+ }
+
+ if (!string.IsNullOrWhiteSpace(trackComposer))
+ {
+ foreach (var composer in trackComposer.Split(InternalValueSeparator))
+ {
+ if (!string.IsNullOrWhiteSpace(composer))
+ {
+ PeopleHelper.AddPerson(people, new PersonInfo
+ {
+ Name = composer,
+ Type = PersonKind.Composer
+ });
+ }
+ }
+ }
+ }
_libraryManager.UpdatePeople(audio, people);
@@ -359,6 +426,33 @@ namespace MediaBrowser.Providers.MediaInfo
}
}
+ // Audiobook-specific metadata: Overview, Publisher, Series
+ if (audio is AudioBook audioBook)
+ {
+ if (!audio.LockedFields.Contains(MetadataField.Overview))
+ {
+ var trackDescription = GetSanitizedStringTag(track.Description, audio.Path);
+ var trackComment = GetSanitizedStringTag(track.Comment, audio.Path);
+ var overview = !string.IsNullOrWhiteSpace(trackDescription) ? trackDescription : trackComment;
+
+ if (!string.IsNullOrWhiteSpace(overview))
+ {
+ if (options.ReplaceAllMetadata || string.IsNullOrEmpty(audio.Overview))
+ {
+ audio.Overview = overview;
+ }
+ }
+ }
+
+ // Publisher → Studio
+ var trackPublisher = GetSanitizedStringTag(track.Publisher, audio.Path);
+ if (!string.IsNullOrWhiteSpace(trackPublisher)
+ && (options.ReplaceAllMetadata || audio.Studios is null || audio.Studios.Length == 0))
+ {
+ audio.SetStudios(new[] { trackPublisher! });
+ }
+ }
+
TryGetSanitizedAdditionalFields(track, "REPLAYGAIN_TRACK_GAIN", out var trackGainTag);
if (trackGainTag is not null)
diff --git a/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs
index c3ff26202f..789df8f061 100644
--- a/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs
@@ -110,7 +110,8 @@ namespace MediaBrowser.Providers.MediaInfo
libraryManager,
_lyricResolver,
lyricManager,
- mediaStreamRepository);
+ mediaStreamRepository,
+ chapterManager);
}
///
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
index 7188e9804e..f1582febf2 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
@@ -12,6 +12,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
@@ -26,19 +27,24 @@ namespace MediaBrowser.Providers.MediaInfo
private readonly ISubtitleManager _subtitleManager;
private readonly ILogger _logger;
private readonly ILocalizationManager _localization;
+ private readonly ISubtitleProvider[] _subtitleProviders;
public SubtitleScheduledTask(
ILibraryManager libraryManager,
IServerConfigurationManager config,
ISubtitleManager subtitleManager,
ILogger logger,
- ILocalizationManager localization)
+ ILocalizationManager localization,
+ IEnumerable subtitleProviders)
{
_libraryManager = libraryManager;
_config = config;
_subtitleManager = subtitleManager;
_logger = logger;
_localization = localization;
+ _subtitleProviders = subtitleProviders
+ .OrderBy(i => i is IHasOrder hasOrder ? hasOrder.Order : 0)
+ .ToArray();
}
public string Name => _localization.GetLocalizedString("TaskDownloadMissingSubtitles");
@@ -76,6 +82,12 @@ namespace MediaBrowser.Providers.MediaInfo
continue;
}
+ if (_subtitleProviders.All(provider => libraryOptions.DisabledSubtitleFetchers.Contains(provider.Name, StringComparer.OrdinalIgnoreCase)))
+ {
+ // Skip this library if all subtitle providers are disabled
+ continue;
+ }
+
subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages;
skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs b/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs
index 3eacc4f0f0..590cf795de 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs
@@ -14,6 +14,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Api
[Authorize]
[Route("[controller]")]
[Produces(MediaTypeNames.Application.Json)]
+ [ApiExplorerSettings(IgnoreApi = true)]
public class TmdbController : ControllerBase
{
private readonly TmdbClientManager _tmdbClientManager;
diff --git a/tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs
index e95df16354..60ed740609 100644
--- a/tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs
+++ b/tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs
@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
-using AutoFixture.Xunit2;
+using AutoFixture.Xunit3;
using Jellyfin.Api.Controllers;
using Jellyfin.Database.Implementations.Entities;
using MediaBrowser.Common.Net;
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index 6b84c4438f..253eab9d79 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -3,15 +3,16 @@
{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}
+ Exe
-
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index 8fef7fde05..f01d522e11 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -3,17 +3,18 @@
{DF194677-DFD3-42AF-9F75-D44D5A416478}
+ Exe
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
index 54d93b48cf..7db94f9c81 100644
--- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
+++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
@@ -3,12 +3,13 @@
{462584F7-5023-4019-9EAC-B98CA458C0A0}
+ Exe
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
index 0364898298..6921fc8a97 100644
--- a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
+++ b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
@@ -1,8 +1,12 @@
+
+ Exe
+
+
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
@@ -11,7 +15,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
diff --git a/tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj b/tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj
index bdf6bc383a..a9b19e0104 100644
--- a/tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj
+++ b/tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj
@@ -1,6 +1,7 @@
net10.0
+ Exe
@@ -14,12 +15,11 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
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 eab003715c..47a116ee42 100644
--- a/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj
@@ -1,8 +1,12 @@
+
+ Exe
+
+
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
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 894bec6aa5..9a58c697f0 100644
--- a/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj
@@ -1,8 +1,12 @@
+
+ Exe
+
+
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
index 6b703e7416..c7065c670a 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
@@ -3,6 +3,7 @@
{28464062-0939-4AA7-9F7B-24DDDA61A7C0}
+ Exe
@@ -14,11 +15,11 @@
-
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
index 8345b610e5..9e2a9a8873 100644
--- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
+++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
@@ -1,15 +1,19 @@
+
+ Exe
+
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
index 7c26494487..1f3e42077f 100644
--- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
+++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
@@ -3,12 +3,13 @@
{3998657B-1CCC-49DD-A19F-275DC8495F57}
+ Exe
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
index 2d7f112109..09ba120a5e 100644
--- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
+++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
@@ -3,17 +3,18 @@
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}
+ Exe
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
index 1263043a51..990544b5a8 100644
--- a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
+++ b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
@@ -1,5 +1,9 @@
+
+ Exe
+
+
PreserveNewest
@@ -9,10 +13,10 @@
-
+
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
index 6997b51ac8..c06279af2d 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
@@ -25,12 +25,12 @@ public class ManagedFileSystemTests
public void MoveDirectory_SameFileSystem_Correct()
=> MoveDirectoryInternal();
- [SkippableFact]
+ [Fact]
public void MoveDirectory_DifferentFileSystem_Correct()
{
const string DestinationParent = "/dev/shm";
- Skip.IfNot(Directory.Exists(DestinationParent));
+ Assert.SkipUnless(Directory.Exists(DestinationParent), $"{DestinationParent} is not available");
MoveDirectoryInternal(DestinationParent);
}
@@ -57,7 +57,7 @@ public class ManagedFileSystemTests
Directory.Delete(destinationDir, true);
}
- [SkippableTheory]
+ [Theory]
[InlineData("/Volumes/Library/Sample/Music/Playlists/", "../Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Music/Beethoven/Misc/Moonlight Sonata.mp3")]
[InlineData("/Volumes/Library/Sample/Music/Playlists/", "../../Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Beethoven/Misc/Moonlight Sonata.mp3")]
[InlineData("/Volumes/Library/Sample/Music/Playlists/", "Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Music/Playlists/Beethoven/Misc/Moonlight Sonata.mp3")]
@@ -67,13 +67,13 @@ public class ManagedFileSystemTests
string filePath,
string expectedAbsolutePath)
{
- Skip.If(OperatingSystem.IsWindows());
+ Assert.SkipWhen(OperatingSystem.IsWindows(), "Unix-only test");
var generatedPath = _sut.MakeAbsolutePath(folderPath, filePath);
Assert.Equal(expectedAbsolutePath, generatedPath);
}
- [SkippableTheory]
+ [Theory]
[InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"..\Beethoven\Misc\Moonlight Sonata.mp3", @"C:\Volumes\Library\Sample\Music\Beethoven\Misc\Moonlight Sonata.mp3")]
[InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"..\..\Beethoven\Misc\Moonlight Sonata.mp3", @"C:\Volumes\Library\Sample\Beethoven\Misc\Moonlight Sonata.mp3")]
[InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"Beethoven\Misc\Moonlight Sonata.mp3", @"C:\Volumes\Library\Sample\Music\Playlists\Beethoven\Misc\Moonlight Sonata.mp3")]
@@ -83,7 +83,7 @@ public class ManagedFileSystemTests
string filePath,
string expectedAbsolutePath)
{
- Skip.IfNot(OperatingSystem.IsWindows());
+ Assert.SkipUnless(OperatingSystem.IsWindows(), "Windows-only test");
var generatedPath = _sut.MakeAbsolutePath(folderPath, filePath);
@@ -100,10 +100,10 @@ public class ManagedFileSystemTests
Assert.Equal(expectedFileName, _sut.GetValidFilename(filename));
}
- [SkippableFact]
+ [Fact]
public void GetFileInfo_DanglingSymlink_ExistsFalse()
{
- Skip.If(OperatingSystem.IsWindows());
+ Assert.SkipWhen(OperatingSystem.IsWindows(), "Unix-only test");
string testFileDir = Path.Combine(Path.GetTempPath(), "jellyfin-test-data");
string testFileName = Path.Combine(testFileDir, Path.GetRandomFileName() + "-danglingsym.link");
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 4e2604e6e1..958ffb8b6e 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -3,6 +3,7 @@
{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}
+ Exe
@@ -16,12 +17,11 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs
index 3d8ea15a31..ede9e61536 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs
@@ -192,13 +192,13 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins
};
var metafilePath = Path.Combine(_pluginPath, "meta.json");
- await File.WriteAllTextAsync(metafilePath, JsonSerializer.Serialize(partial, _options));
+ await File.WriteAllTextAsync(metafilePath, JsonSerializer.Serialize(partial, _options), TestContext.Current.CancellationToken);
var pluginManager = new PluginManager(new NullLogger(), null!, null!, _tempPath, new Version(1, 0));
await pluginManager.PopulateManifest(packageInfo, new Version(1, 0), _pluginPath, PluginStatus.Active);
- var resultBytes = await File.ReadAllBytesAsync(metafilePath);
+ var resultBytes = await File.ReadAllBytesAsync(metafilePath, TestContext.Current.CancellationToken);
var result = JsonSerializer.Deserialize(resultBytes, _options);
Assert.NotNull(result);
@@ -232,7 +232,7 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins
await pluginManager.PopulateManifest(packageInfo, new Version(1, 0), _pluginPath, PluginStatus.Active);
var metafilePath = Path.Combine(_pluginPath, "meta.json");
- var resultBytes = await File.ReadAllBytesAsync(metafilePath);
+ var resultBytes = await File.ReadAllBytesAsync(metafilePath, TestContext.Current.CancellationToken);
var result = JsonSerializer.Deserialize(resultBytes, _options);
Assert.NotNull(result);
@@ -252,13 +252,13 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins
};
var metafilePath = Path.Combine(_pluginPath, "meta.json");
- await File.WriteAllTextAsync(metafilePath, JsonSerializer.Serialize(partial, _options));
+ await File.WriteAllTextAsync(metafilePath, JsonSerializer.Serialize(partial, _options), TestContext.Current.CancellationToken);
var pluginManager = new PluginManager(new NullLogger(), null!, null!, _tempPath, new Version(1, 0));
await pluginManager.PopulateManifest(packageInfo, new Version(1, 0), _pluginPath, PluginStatus.Active);
- var resultBytes = await File.ReadAllBytesAsync(metafilePath);
+ var resultBytes = await File.ReadAllBytesAsync(metafilePath, TestContext.Current.CancellationToken);
var result = JsonSerializer.Deserialize(resultBytes, _options);
Assert.NotNull(result);
@@ -278,13 +278,13 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins
};
var metafilePath = Path.Combine(_pluginPath, "meta.json");
- await File.WriteAllTextAsync(metafilePath, JsonSerializer.Serialize(partial, _options));
+ await File.WriteAllTextAsync(metafilePath, JsonSerializer.Serialize(partial, _options), TestContext.Current.CancellationToken);
var pluginManager = new PluginManager(new NullLogger(), null!, null!, _tempPath, new Version(1, 0));
await pluginManager.PopulateManifest(packageInfo, new Version(1, 0), _pluginPath, PluginStatus.Active);
- var resultBytes = await File.ReadAllBytesAsync(metafilePath);
+ var resultBytes = await File.ReadAllBytesAsync(metafilePath, TestContext.Current.CancellationToken);
var result = JsonSerializer.Deserialize(resultBytes, _options);
Assert.NotNull(result);
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs
index f58a3276ba..92e10c9f92 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs
@@ -51,7 +51,8 @@ namespace Jellyfin.Server.Implementations.Tests.Updates
PackageInfo[] packages = await _installationManager.GetPackages(
"Jellyfin Stable",
"https://repo.jellyfin.org/files/plugin/manifest.json",
- false);
+ false,
+ TestContext.Current.CancellationToken);
Assert.Equal(25, packages.Length);
}
@@ -62,7 +63,8 @@ namespace Jellyfin.Server.Implementations.Tests.Updates
PackageInfo[] packages = await _installationManager.GetPackages(
"Jellyfin Stable",
"https://repo.jellyfin.org/files/plugin/manifest.json",
- false);
+ false,
+ TestContext.Current.CancellationToken);
packages = _installationManager.FilterPackages(packages, "Anime").ToArray();
Assert.Single(packages);
@@ -74,7 +76,8 @@ namespace Jellyfin.Server.Implementations.Tests.Updates
PackageInfo[] packages = await _installationManager.GetPackages(
"Jellyfin Stable",
"https://repo.jellyfin.org/files/plugin/manifest.json",
- false);
+ false,
+ TestContext.Current.CancellationToken);
packages = _installationManager.FilterPackages(packages, id: new Guid("a4df60c5-6ab4-412a-8f79-2cab93fb2bc5")).ToArray();
Assert.Single(packages);
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs
index 96ca96558d..ef084430e8 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs
@@ -21,7 +21,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.GetAsync("System/ActivityLog/Entries");
+ var response = await client.GetAsync("System/ActivityLog/Entries", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs
index 8761cf69bc..1973af3f25 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs
@@ -25,13 +25,13 @@ namespace Jellyfin.Server.Integration.Tests
var client = _factory.CreateClient();
// Act
- var response = await client.GetAsync("/Branding/Configuration");
+ var response = await client.GetAsync("/Branding/Configuration", TestContext.Current.CancellationToken);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
- await response.Content.ReadFromJsonAsync();
+ await response.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken);
}
[Theory]
@@ -43,7 +43,7 @@ namespace Jellyfin.Server.Integration.Tests
var client = _factory.CreateClient();
// Act
- var response = await client.GetAsync(url);
+ var response = await client.GetAsync(url, TestContext.Current.CancellationToken);
// Assert
Assert.True(response.IsSuccessStatusCode);
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
index d92dbbd732..32bdc57265 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
@@ -27,7 +27,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
{
var client = _factory.CreateClient();
- var response = await client.GetAsync("web/ConfigurationPage?name=ThisPageDoesntExists");
+ var response = await client.GetAsync("web/ConfigurationPage?name=ThisPageDoesntExists", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -37,12 +37,12 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
{
var client = _factory.CreateClient();
- var response = await client.GetAsync("/web/ConfigurationPage?name=TestPlugin");
+ var response = await client.GetAsync("/web/ConfigurationPage?name=TestPlugin", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Text.Html, response.Content.Headers.ContentType?.MediaType);
StreamReader reader = new StreamReader(typeof(TestPlugin).Assembly.GetManifestResourceStream("Jellyfin.Server.Integration.Tests.TestPage.html")!);
- Assert.Equal(await response.Content.ReadAsStringAsync(), await reader.ReadToEndAsync());
+ Assert.Equal(await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken), await reader.ReadToEndAsync(TestContext.Current.CancellationToken));
}
[Fact]
@@ -50,7 +50,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
{
var client = _factory.CreateClient();
- var response = await client.GetAsync("/web/ConfigurationPage?name=BrokenPage");
+ var response = await client.GetAsync("/web/ConfigurationPage?name=BrokenPage", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -61,11 +61,11 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.GetAsync("/web/ConfigurationPages");
+ var response = await client.GetAsync("/web/ConfigurationPages", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- _ = await response.Content.ReadFromJsonAsync(_jsonOptions);
+ _ = await response.Content.ReadFromJsonAsync(_jsonOptions, TestContext.Current.CancellationToken);
// TODO: check content
}
@@ -75,13 +75,13 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.GetAsync("/web/ConfigurationPages?enableInMainMenu=true");
+ var response = await client.GetAsync("/web/ConfigurationPages?enableInMainMenu=true", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
- var data = await response.Content.ReadFromJsonAsync(_jsonOptions);
+ var data = await response.Content.ReadFromJsonAsync(_jsonOptions, TestContext.Current.CancellationToken);
Assert.NotNull(data);
Assert.Empty(data);
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs
index 64b9bd8e16..165e269814 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs
@@ -28,7 +28,7 @@ public sealed class ItemsControllerTests : IClassFixture>(_jsonOptions);
+ var items = await response.Content.ReadFromJsonAsync>(_jsonOptions, TestContext.Current.CancellationToken);
Assert.NotNull(items);
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryControllerTests.cs
index 6881a92101..edbb46b34c 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryControllerTests.cs
@@ -34,7 +34,7 @@ public sealed class LibraryControllerTests : IClassFixture
{
private readonly JellyfinApplicationFactory _factory;
@@ -40,7 +40,7 @@ public sealed class LibraryStructureControllerTests : IClassFixture(_jsonOptions)
- .FirstOrDefaultAsync(x => string.Equals(x?.Name, "test", StringComparison.Ordinal));
+ var library = await response.Content.ReadFromJsonAsAsyncEnumerable(_jsonOptions, TestContext.Current.CancellationToken)
+ .FirstOrDefaultAsync(x => string.Equals(x?.Name, "test", StringComparison.Ordinal), TestContext.Current.CancellationToken);
Assert.NotNull(library);
var options = library.LibraryOptions;
@@ -99,7 +99,7 @@ public sealed class LibraryStructureControllerTests : IClassFixture();
+ var responseBody = await response.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken);
Assert.NotNull(responseBody);
Assert.Equal(body.Type, responseBody.Type);
Assert.Equal(body.Url, responseBody.Url);
@@ -72,7 +72,7 @@ public sealed class LiveTvControllerTests : IClassFixture());
- var response = await client.PostAsync("Library/VirtualFolders/Name?name=+&newName=test", postContent);
+ var response = await client.PostAsync("Library/VirtualFolders/Name?name=+&newName=test", postContent, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
@@ -41,7 +41,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
using var postContent = new ByteArrayContent(Array.Empty());
- var response = await client.PostAsync("Library/VirtualFolders/Name?name=test&newName=+", postContent);
+ var response = await client.PostAsync("Library/VirtualFolders/Name?name=test&newName=+", postContent, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
@@ -53,7 +53,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
using var postContent = new ByteArrayContent(Array.Empty());
- var response = await client.PostAsync("Library/VirtualFolders/Name?name=doesnt+exist&newName=test", postContent);
+ var response = await client.PostAsync("Library/VirtualFolders/Name?name=doesnt+exist&newName=test", postContent, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -70,7 +70,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Path = "/this/path/doesnt/exist"
};
- var response = await client.PostAsJsonAsync("Library/VirtualFolders/Paths", data, _jsonOptions);
+ var response = await client.PostAsJsonAsync("Library/VirtualFolders/Paths", data, _jsonOptions, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -87,7 +87,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
PathInfo = new MediaPathInfo("test")
};
- var response = await client.PostAsJsonAsync("Library/VirtualFolders/Paths/Update", data, _jsonOptions);
+ var response = await client.PostAsJsonAsync("Library/VirtualFolders/Paths/Update", data, _jsonOptions, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
@@ -98,7 +98,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.DeleteAsync("Library/VirtualFolders/Paths?name=+");
+ var response = await client.DeleteAsync("Library/VirtualFolders/Paths?name=+", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
@@ -109,7 +109,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.DeleteAsync("Library/VirtualFolders/Paths?name=none&path=%2Fthis%2Fpath%2Fdoesnt%2Fexist");
+ var response = await client.DeleteAsync("Library/VirtualFolders/Paths?name=none&path=%2Fthis%2Fpath%2Fdoesnt%2Fexist", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/MusicGenreControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/MusicGenreControllerTests.cs
index f9982cf12b..3e14850613 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/MusicGenreControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/MusicGenreControllerTests.cs
@@ -20,7 +20,7 @@ public sealed class MusicGenreControllerTests : IClassFixture
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- using var response = await client.GetAsync($"Persons/DoesntExist");
+ using var response = await client.GetAsync($"Persons/DoesntExist", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/PlaystateControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/PlaystateControllerTests.cs
index 3b9ed17787..db271fc5cd 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/PlaystateControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/PlaystateControllerTests.cs
@@ -21,7 +21,7 @@ public class PlaystateControllerTests : IClassFixture(JsonDefaults.Options);
+ _ = await response.Content.ReadFromJsonAsync(JsonDefaults.Options, TestContext.Current.CancellationToken);
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
index c8ae2a88af..0e5d81a4d6 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
@@ -8,11 +8,11 @@ using System.Threading.Tasks;
using Jellyfin.Api.Models.StartupDtos;
using Jellyfin.Extensions.Json;
using Xunit;
-using Xunit.Priority;
+using Xunit.v3.Priority;
namespace Jellyfin.Server.Integration.Tests.Controllers
{
- [TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
+ [TestCaseOrderer(typeof(PriorityOrderer))]
public sealed class StartupControllerTests : IClassFixture
{
private readonly JellyfinApplicationFactory _factory;
@@ -37,14 +37,14 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
PreferredMetadataLanguage = "nl"
};
- using var postResponse = await client.PostAsJsonAsync("/Startup/Configuration", config, _jsonOptions);
+ using var postResponse = await client.PostAsJsonAsync("/Startup/Configuration", config, _jsonOptions, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode);
- using var getResponse = await client.GetAsync("/Startup/Configuration");
+ using var getResponse = await client.GetAsync("/Startup/Configuration", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, getResponse.Content.Headers.ContentType?.MediaType);
- var newConfig = await getResponse.Content.ReadFromJsonAsync(_jsonOptions);
+ var newConfig = await getResponse.Content.ReadFromJsonAsync(_jsonOptions, TestContext.Current.CancellationToken);
Assert.Equal(config.ServerName, newConfig!.ServerName);
Assert.Equal(config.UICulture, newConfig.UICulture);
Assert.Equal(config.MetadataCountryCode, newConfig.MetadataCountryCode);
@@ -57,11 +57,11 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
{
var client = _factory.CreateClient();
- using var response = await client.GetAsync("/Startup/User");
+ using var response = await client.GetAsync("/Startup/User", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
- var user = await response.Content.ReadFromJsonAsync(_jsonOptions);
+ var user = await response.Content.ReadFromJsonAsync(_jsonOptions, TestContext.Current.CancellationToken);
Assert.NotNull(user);
Assert.NotNull(user.Name);
Assert.NotEmpty(user.Name);
@@ -80,14 +80,14 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Password = "NewPassword"
};
- var postResponse = await client.PostAsJsonAsync("/Startup/User", user, _jsonOptions);
+ var postResponse = await client.PostAsJsonAsync("/Startup/User", user, _jsonOptions, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode);
- var getResponse = await client.GetAsync("/Startup/User");
+ var getResponse = await client.GetAsync("/Startup/User", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, getResponse.Content.Headers.ContentType?.MediaType);
- var newUser = await getResponse.Content.ReadFromJsonAsync(_jsonOptions);
+ var newUser = await getResponse.Content.ReadFromJsonAsync(_jsonOptions, TestContext.Current.CancellationToken);
Assert.NotNull(newUser);
Assert.Equal(user.Name, newUser.Name);
Assert.Null(newUser.Password);
@@ -99,7 +99,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
{
var client = _factory.CreateClient();
- var response = await client.PostAsync("/Startup/Complete", new ByteArrayContent(Array.Empty()));
+ var response = await client.PostAsync("/Startup/Complete", new ByteArrayContent(Array.Empty()), TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}
@@ -109,7 +109,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
{
var client = _factory.CreateClient();
- using var response = await client.GetAsync("/Startup/User");
+ using var response = await client.GetAsync("/Startup/User", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
index 04d1b3dc27..7ea56be731 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
@@ -10,11 +10,11 @@ using Jellyfin.Api.Models.UserDtos;
using Jellyfin.Extensions.Json;
using MediaBrowser.Model.Dto;
using Xunit;
-using Xunit.Priority;
+using Xunit.v3.Priority;
namespace Jellyfin.Server.Integration.Tests.Controllers
{
- [TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
+ [TestCaseOrderer(typeof(PriorityOrderer))]
public sealed class UserControllerTests : IClassFixture
{
private const string TestUsername = "testUser01";
@@ -41,9 +41,9 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
{
var client = _factory.CreateClient();
- using var response = await client.GetAsync("Users/Public");
+ using var response = await client.GetAsync("Users/Public", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var users = await response.Content.ReadFromJsonAsync(_jsonOptions);
+ var users = await response.Content.ReadFromJsonAsync(_jsonOptions, TestContext.Current.CancellationToken);
// User are hidden by default
Assert.NotNull(users);
Assert.Empty(users);
@@ -56,9 +56,9 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- using var response = await client.GetAsync("Users");
+ using var response = await client.GetAsync("Users", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var users = await response.Content.ReadFromJsonAsync(_jsonOptions);
+ var users = await response.Content.ReadFromJsonAsync(_jsonOptions, TestContext.Current.CancellationToken);
Assert.NotNull(users);
Assert.Single(users);
}
@@ -89,7 +89,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
using var response = await CreateUserByName(client, createRequest);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var user = await response.Content.ReadFromJsonAsync(_jsonOptions);
+ var user = await response.Content.ReadFromJsonAsync(_jsonOptions, TestContext.Current.CancellationToken);
Assert.Equal(TestUsername, user!.Name);
_testUserId = user.Id;
@@ -128,7 +128,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
// access token can't be null here as the previous test populated it
client.DefaultRequestHeaders.AddAuthHeader(_accessToken!);
- using var response = await client.DeleteAsync($"User/{Guid.NewGuid()}");
+ using var response = await client.DeleteAsync($"User/{Guid.NewGuid()}", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs
index 98ad28f5bd..6e4fccd735 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs
@@ -28,7 +28,7 @@ public sealed class UserLibraryControllerTests : IClassFixture(_jsonOptions);
+ var rootDto = await response.Content.ReadFromJsonAsync(_jsonOptions, TestContext.Current.CancellationToken);
Assert.NotNull(rootDto);
}
@@ -99,9 +99,9 @@ public sealed class UserLibraryControllerTests : IClassFixture>(_jsonOptions);
+ var rootDto = await response.Content.ReadFromJsonAsync>(_jsonOptions, TestContext.Current.CancellationToken);
Assert.NotNull(rootDto);
}
@@ -116,9 +116,9 @@ public sealed class UserLibraryControllerTests : IClassFixture(_jsonOptions);
+ var rootDto = await response.Content.ReadFromJsonAsync(_jsonOptions, TestContext.Current.CancellationToken);
Assert.NotNull(rootDto);
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/VideosControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/VideosControllerTests.cs
index 1916ced12c..e0630ff443 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/VideosControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/VideosControllerTests.cs
@@ -21,7 +21,7 @@ public sealed class VideosControllerTests : IClassFixture
+
+ Exe
+
+
-
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/tests/Jellyfin.Server.Integration.Tests/Middleware/RobotsRedirectionMiddlewareTests.cs b/tests/Jellyfin.Server.Integration.Tests/Middleware/RobotsRedirectionMiddlewareTests.cs
index 1ea79f7deb..baeaf4d0cb 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Middleware/RobotsRedirectionMiddlewareTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Middleware/RobotsRedirectionMiddlewareTests.cs
@@ -23,7 +23,7 @@ namespace Jellyfin.Server.Integration.Tests.Middleware
AllowAutoRedirect = false
});
- var response = await client.GetAsync("robots.txt");
+ var response = await client.GetAsync("robots.txt", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal("web/robots.txt", response.Headers.Location?.ToString());
diff --git a/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs
index 62cdd25aec..17a8a55222 100644
--- a/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs
@@ -3,7 +3,6 @@ using System.Reflection;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using Xunit;
-using Xunit.Abstractions;
namespace Jellyfin.Server.Integration.Tests
{
@@ -25,7 +24,7 @@ namespace Jellyfin.Server.Integration.Tests
var client = _factory.CreateClient();
// Act
- var response = await client.GetAsync("/api-docs/openapi.json");
+ var response = await client.GetAsync("/api-docs/openapi.json", TestContext.Current.CancellationToken);
// Assert
response.EnsureSuccessStatusCode();
@@ -35,7 +34,7 @@ namespace Jellyfin.Server.Integration.Tests
string outputPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "openapi.json"));
_outputHelper.WriteLine("Writing OpenAPI Spec JSON to '{0}'.", outputPath);
await using var fs = AsyncFile.Create(outputPath);
- await response.Content.CopyToAsync(fs);
+ await response.Content.CopyToAsync(fs, TestContext.Current.CancellationToken);
}
}
}
diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
index 21596e0ed2..3ad5310c6b 100644
--- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
+++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
@@ -1,12 +1,16 @@
+
+ Exe
+
+
-
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
index 14f4c33b6b..e788f43b86 100644
--- a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
+++ b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
@@ -23,8 +23,8 @@ namespace Jellyfin.Server.Tests
true,
true,
new string[] { "192.168.t", "127.0.0.1", "::1", "1234.1232.12.1234" },
- new IPAddress[] { IPAddress.Loopback },
- new IPNetwork[] { new IPNetwork(IPAddress.IPv6Loopback, 128) });
+ new IPAddress[] { IPAddress.Loopback, IPAddress.IPv6Loopback },
+ Array.Empty());
data.Add(
true,
@@ -37,8 +37,8 @@ namespace Jellyfin.Server.Tests
true,
true,
new string[] { "::1" },
- Array.Empty(),
- new IPNetwork[] { new IPNetwork(IPAddress.IPv6Loopback, 128) });
+ new IPAddress[] { IPAddress.IPv6Loopback },
+ Array.Empty());
data.Add(
false,
@@ -58,15 +58,15 @@ namespace Jellyfin.Server.Tests
false,
true,
new string[] { "localhost" },
- Array.Empty(),
- new IPNetwork[] { new IPNetwork(IPAddress.IPv6Loopback, 128) });
+ new IPAddress[] { IPAddress.IPv6Loopback },
+ Array.Empty());
data.Add(
true,
true,
new string[] { "localhost" },
- new IPAddress[] { IPAddress.Loopback },
- new IPNetwork[] { new IPNetwork(IPAddress.IPv6Loopback, 128) });
+ new IPAddress[] { IPAddress.Loopback, IPAddress.IPv6Loopback },
+ Array.Empty());
return data;
}
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
index 9fe0744de1..3b39fe72d6 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
@@ -1,5 +1,9 @@
+
+ Exe
+
+
PreserveNewest
@@ -9,7 +13,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive