aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs9
-rw-r--r--Emby.Server.Implementations/Localization/Core/ar.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/en-US.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/eo.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/es.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/es_419.json14
-rw-r--r--Emby.Server.Implementations/Localization/Core/et.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/fa.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/hr.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/hu.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/ja.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/lt-LT.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/mk.json7
-rw-r--r--Emby.Server.Implementations/Localization/Core/mn.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/ms.json46
-rw-r--r--Emby.Server.Implementations/Localization/Core/ne.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/nl.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/pa.json14
-rw-r--r--Emby.Server.Implementations/Localization/Core/pl.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/pr.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/pt-PT.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/ro.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/sq.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/sr.json7
-rw-r--r--Emby.Server.Implementations/Localization/Core/sv.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/ta.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/te.json24
-rw-r--r--Emby.Server.Implementations/Localization/Core/vi.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-CN.json2
-rw-r--r--Jellyfin.Api/Helpers/ProgressiveFileStream.cs76
-rw-r--r--Jellyfin.Api/Helpers/StreamingHelpers.cs1
-rw-r--r--Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs2
-rw-r--r--Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs2
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj4
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs1
-rw-r--r--MediaBrowser.Model/Configuration/LibraryOptions.cs1
-rw-r--r--MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs15
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj4
-rw-r--r--MediaBrowser.Model/Net/MimeTypes.cs149
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs54
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs54
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs41
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs20
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html64
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs68
-rw-r--r--MediaBrowser.Providers/TV/SeriesMetadataService.cs7
-rw-r--r--debian/jellyfin.service15
-rw-r--r--tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs164
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs31
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs4
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs6
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs13
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs9
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs9
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs13
55 files changed, 701 insertions, 314 deletions
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index 08aa0cfd7..93d72dba4 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -9,6 +9,7 @@ using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
+using System.Net.Http.Json;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Security.Cryptography;
@@ -101,11 +102,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
};
- var requestString = JsonSerializer.Serialize(requestList, _jsonOptions);
- _logger.LogDebug("Request string for schedules is: {RequestString}", requestString);
+ _logger.LogDebug("Request string for schedules is: {@RequestString}", requestList);
using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/schedules");
- options.Content = new StringContent(requestString, Encoding.UTF8, MediaTypeNames.Application.Json);
+ options.Content = JsonContent.Create(requestList, options: _jsonOptions);
options.Headers.TryAddWithoutValidation("token", token);
using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
@@ -121,8 +121,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
programRequestOptions.Headers.TryAddWithoutValidation("token", token);
var programIds = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct();
- programRequestOptions.Content = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(programIds, _jsonOptions));
- programRequestOptions.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
+ programRequestOptions.Content = JsonContent.Create(programIds, options: _jsonOptions);
using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false);
await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json
index a83a453b4..9d4d40e51 100644
--- a/Emby.Server.Implementations/Localization/Core/ar.json
+++ b/Emby.Server.Implementations/Localization/Core/ar.json
@@ -11,11 +11,11 @@
"Collections": "التجميعات",
"DeviceOfflineWithName": "قُطِع الاتصال ب{0}",
"DeviceOnlineWithName": "{0} متصل",
- "FailedLoginAttemptWithUserName": "عملية تسجيل الدخول فشلت من {0}",
+ "FailedLoginAttemptWithUserName": "محاولة تسجيل الدخول فشلت من {0}",
"Favorites": "مفضلات",
"Folders": "المجلدات",
"Genres": "التضنيفات",
- "HeaderAlbumArtists": "ألبوم الفنان",
+ "HeaderAlbumArtists": "فناني الألبوم",
"HeaderContinueWatching": "استمر بالمشاهدة",
"HeaderFavoriteAlbums": "الألبومات المفضلة",
"HeaderFavoriteArtists": "الفنانون المفضلون",
diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json
index 71a43f93a..568a8e447 100644
--- a/Emby.Server.Implementations/Localization/Core/en-US.json
+++ b/Emby.Server.Implementations/Localization/Core/en-US.json
@@ -12,7 +12,7 @@
"Default": "Default",
"DeviceOfflineWithName": "{0} has disconnected",
"DeviceOnlineWithName": "{0} is connected",
- "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
+ "FailedLoginAttemptWithUserName": "Failed login try from {0}",
"Favorites": "Favorites",
"Folders": "Folders",
"Forced": "Forced",
diff --git a/Emby.Server.Implementations/Localization/Core/eo.json b/Emby.Server.Implementations/Localization/Core/eo.json
index 3cadde0a0..8abf7fa66 100644
--- a/Emby.Server.Implementations/Localization/Core/eo.json
+++ b/Emby.Server.Implementations/Localization/Core/eo.json
@@ -104,7 +104,7 @@
"TaskRefreshChannelsDescription": "Refreŝigas informon pri interretaj kanaloj.",
"TaskDownloadMissingSubtitles": "Elŝuti mankantajn subtekstojn",
"TaskCleanTranscode": "Malplenigi Transkodadan Katalogon",
- "TaskRefreshChapterImages": "Eltiri Ĉapitraj Bildojn",
+ "TaskRefreshChapterImages": "Eltiri Ĉapitrajn Bildojn",
"TaskCleanCache": "Malplenigi Staplan Katalogon",
"TaskCleanActivityLog": "Malplenigi Aktivecan Ĵurnalon",
"PluginUpdatedWithName": "{0} estis ĝisdatigita",
diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json
index d3d9d2703..f8c69712e 100644
--- a/Emby.Server.Implementations/Localization/Core/es.json
+++ b/Emby.Server.Implementations/Localization/Core/es.json
@@ -15,8 +15,8 @@
"Favorites": "Favoritos",
"Folders": "Carpetas",
"Genres": "Géneros",
- "HeaderAlbumArtists": "Artista del álbum",
- "HeaderContinueWatching": "Continuar viendo",
+ "HeaderAlbumArtists": "Artistas del álbum",
+ "HeaderContinueWatching": "Seguir viendo",
"HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos",
"HeaderFavoriteEpisodes": "Episodios favoritos",
diff --git a/Emby.Server.Implementations/Localization/Core/es_419.json b/Emby.Server.Implementations/Localization/Core/es_419.json
index a968c6dab..2ca736ad9 100644
--- a/Emby.Server.Implementations/Localization/Core/es_419.json
+++ b/Emby.Server.Implementations/Localization/Core/es_419.json
@@ -15,7 +15,7 @@
"HeaderFavoriteEpisodes": "Episodios favoritos",
"HeaderFavoriteShows": "Programas favoritos",
"HeaderContinueWatching": "Continuar viendo",
- "HeaderAlbumArtists": "Artistas del álbum",
+ "HeaderAlbumArtists": "Artistas de álbum",
"Genres": "Géneros",
"Folders": "Carpetas",
"Favorites": "Favoritos",
@@ -29,7 +29,7 @@
"TaskRefreshChannelsDescription": "Actualiza la información de canales de Internet.",
"TaskRefreshChannels": "Actualizar canales",
"TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día.",
- "TaskCleanTranscode": "Limpiar directorio de transcodificado",
+ "TaskCleanTranscode": "Limpiar el directorio de transcodificaciones",
"TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.",
"TaskUpdatePlugins": "Actualizar complementos",
"TaskRefreshPeopleDescription": "Actualiza metadatos de actores y directores en tu biblioteca de medios.",
@@ -105,7 +105,7 @@
"Inherit": "Heredar",
"HomeVideos": "Videos caseros",
"HeaderRecordingGroups": "Grupos de grabación",
- "FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión desde {0}",
+ "FailedLoginAttemptWithUserName": "Intento de inicio de sesión fallido desde {0}",
"DeviceOnlineWithName": "{0} está conectado",
"DeviceOfflineWithName": "{0} se ha desconectado",
"ChapterNameValue": "Capítulo {0}",
@@ -114,10 +114,10 @@
"Application": "Aplicación",
"AppDeviceValues": "App: {0}, Dispositivo: {1}",
"TaskCleanActivityLogDescription": "Elimina las entradas del registro de actividad anteriores al periodo configurado.",
- "TaskCleanActivityLog": "Limpiar Registro de Actividades",
+ "TaskCleanActivityLog": "Limpiar registro de actividades",
"Undefined": "Sin definir",
"Forced": "Forzado",
- "Default": "Por Defecto",
- "TaskOptimizeDatabaseDescription": "Compacta la base de datos y restaura el espacio libre. Ejecutar esta tarea después de actualizar las librerías o realizar otros cambios que impliquen modificar las bases de datos puede mejorar la performance.",
- "TaskOptimizeDatabase": "Optimización de base de datos"
+ "Default": "Por defecto",
+ "TaskOptimizeDatabaseDescription": "Compacta la base de datos y libera espacio. Ejecutar esta tarea después de escanear la biblioteca o hacer otros cambios que impliquen modificaciones en la base de datos puede mejorar el rendimiento.",
+ "TaskOptimizeDatabase": "Optimizar base de datos"
}
diff --git a/Emby.Server.Implementations/Localization/Core/et.json b/Emby.Server.Implementations/Localization/Core/et.json
index 71f4a97e5..8db6a0b38 100644
--- a/Emby.Server.Implementations/Localization/Core/et.json
+++ b/Emby.Server.Implementations/Localization/Core/et.json
@@ -102,7 +102,7 @@
"Forced": "Sunnitud",
"Folders": "Kaustad",
"Favorites": "Lemmikud",
- "FailedLoginAttemptWithUserName": "Ebaõnnestunud sisselogimiskatse kasutajalt {0}",
+ "FailedLoginAttemptWithUserName": "{0} - sisselogimine nurjus",
"DeviceOnlineWithName": "{0} on ühendatud",
"DeviceOfflineWithName": "{0} katkestas ühenduse",
"Default": "Vaikimisi",
diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json
index 8ab657e5b..3d3b3533f 100644
--- a/Emby.Server.Implementations/Localization/Core/fa.json
+++ b/Emby.Server.Implementations/Localization/Core/fa.json
@@ -6,7 +6,7 @@
"AuthenticationSucceededWithUserName": "{0} با موفقیت تایید اعتبار شد",
"Books": "کتاب‌ها",
"CameraImageUploadedFrom": "یک عکس جدید از دوربین ارسال شده است {0}",
- "Channels": "کانال‌ها",
+ "Channels": "کانالها",
"ChapterNameValue": "قسمت {0}",
"Collections": "مجموعه‌ها",
"DeviceOfflineWithName": "ارتباط {0} قطع شد",
@@ -37,7 +37,7 @@
"MessageNamedServerConfigurationUpdatedWithValue": "پکربندی بخش {0} سرور بروزرسانی شد",
"MessageServerConfigurationUpdated": "پیکربندی سرور بروزرسانی شد",
"MixedContent": "محتوای مخلوط",
- "Movies": "فیلم‌ها",
+ "Movies": "فیلم ها",
"Music": "موسیقی",
"MusicVideos": "موزیک ویدیوها",
"NameInstallFailed": "{0} نصب با مشکل مواجه شد",
diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json
index d7cda61da..4df0444e6 100644
--- a/Emby.Server.Implementations/Localization/Core/hr.json
+++ b/Emby.Server.Implementations/Localization/Core/hr.json
@@ -15,7 +15,7 @@
"Favorites": "Favoriti",
"Folders": "Mape",
"Genres": "Žanrovi",
- "HeaderAlbumArtists": "Album od izvođača",
+ "HeaderAlbumArtists": "Izvođači albuma",
"HeaderContinueWatching": "Nastavi gledati",
"HeaderFavoriteAlbums": "Omiljeni albumi",
"HeaderFavoriteArtists": "Omiljeni izvođači",
diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json
index 79a692b9b..acde84aaf 100644
--- a/Emby.Server.Implementations/Localization/Core/hu.json
+++ b/Emby.Server.Implementations/Localization/Core/hu.json
@@ -11,7 +11,7 @@
"Collections": "Gyűjtemények",
"DeviceOfflineWithName": "{0} kijelentkezett",
"DeviceOnlineWithName": "{0} belépett",
- "FailedLoginAttemptWithUserName": "Sikertelen bejelentkezési kísérlet tőle: {0}",
+ "FailedLoginAttemptWithUserName": "Sikertelen bejelentkezési kísérlet innen: {0}",
"Favorites": "Kedvencek",
"Folders": "Könyvtárak",
"Genres": "Műfajok",
diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json
index c689bc58a..7f41561ec 100644
--- a/Emby.Server.Implementations/Localization/Core/ja.json
+++ b/Emby.Server.Implementations/Localization/Core/ja.json
@@ -11,11 +11,11 @@
"Collections": "コレクション",
"DeviceOfflineWithName": "{0} が切断されました",
"DeviceOnlineWithName": "{0} が接続されました",
- "FailedLoginAttemptWithUserName": "ログインを試行しましたが {0}によって失敗しました",
+ "FailedLoginAttemptWithUserName": "ログインを試行しましたが {0} によって失敗しました",
"Favorites": "お気に入り",
"Folders": "フォルダー",
"Genres": "ジャンル",
- "HeaderAlbumArtists": "アーティストのアルバム",
+ "HeaderAlbumArtists": "アルバムアーティスト",
"HeaderContinueWatching": "視聴を続ける",
"HeaderFavoriteAlbums": "お気に入りのアルバム",
"HeaderFavoriteArtists": "お気に入りのアーティスト",
diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json
index f3a131d40..f0a07f604 100644
--- a/Emby.Server.Implementations/Localization/Core/lt-LT.json
+++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json
@@ -1,7 +1,7 @@
{
"Albums": "Albumai",
"AppDeviceValues": "Programa: {0}, Įrenginys: {1}",
- "Application": "Programa",
+ "Application": "Programėlė",
"Artists": "Atlikėjai",
"AuthenticationSucceededWithUserName": "{0} sėkmingai autentifikuota",
"Books": "Knygos",
diff --git a/Emby.Server.Implementations/Localization/Core/mk.json b/Emby.Server.Implementations/Localization/Core/mk.json
index 6baedcb2d..279734c5e 100644
--- a/Emby.Server.Implementations/Localization/Core/mk.json
+++ b/Emby.Server.Implementations/Localization/Core/mk.json
@@ -5,7 +5,7 @@
"PluginUninstalledWithName": "{0} беше успешно деинсталирано",
"PluginInstalledWithName": "{0} беше успешно инсталирано",
"Plugin": "Додатоци",
- "Playlists": "Листи",
+ "Playlists": "Плејлисти",
"Photos": "Слики",
"NotificationOptionVideoPlaybackStopped": "Видео стопирано",
"NotificationOptionVideoPlayback": "Видео пуштено",
@@ -97,5 +97,8 @@
"TasksChannelsCategory": "Интернет Канали",
"TasksApplicationCategory": "Апликација",
"TasksLibraryCategory": "Библиотека",
- "TasksMaintenanceCategory": "Одржување"
+ "TasksMaintenanceCategory": "Одржување",
+ "Undefined": "Недефинирано",
+ "Forced": "Принудно",
+ "Default": "Зададено"
}
diff --git a/Emby.Server.Implementations/Localization/Core/mn.json b/Emby.Server.Implementations/Localization/Core/mn.json
index ab7d423b5..7421d42fb 100644
--- a/Emby.Server.Implementations/Localization/Core/mn.json
+++ b/Emby.Server.Implementations/Localization/Core/mn.json
@@ -9,5 +9,6 @@
"Genres": "Төрөл зүйл",
"Favorites": "Дуртай",
"Collections": "Багц",
- "Artists": "Жүжигчин"
+ "Artists": "Зураачуд",
+ "Albums": "Цомгууд"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json
index b2dcf270c..2e0fbc366 100644
--- a/Emby.Server.Implementations/Localization/Core/ms.json
+++ b/Emby.Server.Implementations/Localization/Core/ms.json
@@ -37,9 +37,9 @@
"MessageNamedServerConfigurationUpdatedWithValue": "Konfigurasi pelayan di bahagian {0} telah dikemas kini",
"MessageServerConfigurationUpdated": "Konfigurasi pelayan telah dikemas kini",
"MixedContent": "Kandungan campuran",
- "Movies": "Filem",
+ "Movies": "Filem-filem",
"Music": "Muzik",
- "MusicVideos": "Muzik video",
+ "MusicVideos": "",
"NameInstallFailed": "{0} pemasangan gagal",
"NameSeasonNumber": "Musim {0}",
"NameSeasonUnknown": "Musim Tidak Diketahui",
@@ -53,43 +53,43 @@
"NotificationOptionNewLibraryContent": "Kandungan baru telah ditambah",
"NotificationOptionPluginError": "Kegagalan plugin",
"NotificationOptionPluginInstalled": "Plugin telah dipasang",
- "NotificationOptionPluginUninstalled": "Plugin uninstalled",
- "NotificationOptionPluginUpdateInstalled": "Plugin update installed",
- "NotificationOptionServerRestartRequired": "Server restart required",
- "NotificationOptionTaskFailed": "Scheduled task failure",
- "NotificationOptionUserLockedOut": "User locked out",
- "NotificationOptionVideoPlayback": "Video playback started",
+ "NotificationOptionPluginUninstalled": "Plugin telah dinyahpasang",
+ "NotificationOptionPluginUpdateInstalled": "Kemaskini plugin telah dipasang",
+ "NotificationOptionServerRestartRequired": "",
+ "NotificationOptionTaskFailed": "Kegagalan tugas berjadual",
+ "NotificationOptionUserLockedOut": "Pengguna telah dikunci",
+ "NotificationOptionVideoPlayback": "Ulangmain video bermula",
"NotificationOptionVideoPlaybackStopped": "Ulangmain video dihentikan",
"Photos": "Gambar-gambar",
"Playlists": "Senarai main",
"Plugin": "Plugin",
- "PluginInstalledWithName": "{0} was installed",
- "PluginUninstalledWithName": "{0} was uninstalled",
- "PluginUpdatedWithName": "{0} was updated",
- "ProviderValue": "Provider: {0}",
+ "PluginInstalledWithName": "{0} telah dipasang",
+ "PluginUninstalledWithName": "{0} telah dinyahpasang",
+ "PluginUpdatedWithName": "{0} telah dikemaskini",
+ "ProviderValue": "Pembekal: {0}",
"ScheduledTaskFailedWithName": "{0} gagal",
"ScheduledTaskStartedWithName": "{0} bermula",
- "ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
- "Shows": "Series",
+ "ServerNameNeedsToBeRestarted": "{0} perlu di ulangmula",
+ "Shows": "Tayangan",
"Songs": "Lagu-lagu",
"StartupEmbyServerIsLoading": "Pelayan Jellyfin sedang dimuatkan. Sila cuba sebentar lagi.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "Muat turun sarikata gagal dari {0} untuk {1}",
- "Sync": "Sync",
+ "Sync": "",
"System": "Sistem",
- "TvShows": "TV Shows",
- "User": "User",
- "UserCreatedWithName": "User {0} has been created",
- "UserDeletedWithName": "User {0} has been deleted",
- "UserDownloadingItemWithValues": "{0} is downloading {1}",
+ "TvShows": "Tayangan TV",
+ "User": "Pengguna",
+ "UserCreatedWithName": "Pengguna {0} telah diwujudkan",
+ "UserDeletedWithName": "Pengguna {0} telah dipadamkan",
+ "UserDownloadingItemWithValues": "{0} sedang memuat turun {1}",
"UserLockedOutWithName": "Pengguna {0} telah dikunci",
"UserOfflineFromDevice": "{0} telah terputus dari {1}",
"UserOnlineFromDevice": "{0} berada dalam talian dari {1}",
"UserPasswordChangedWithName": "Kata laluan telah ditukar bagi pengguna {0}",
"UserPolicyUpdatedWithName": "Dasar pengguna telah dikemas kini untuk {0}",
- "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}",
- "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
- "ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
+ "UserStartedPlayingItemWithValues": "{0} sedang dimainkan {1} pada {2}",
+ "UserStoppedPlayingItemWithValues": "{0} telah tamat dimainkan {1} pada {2}",
+ "ValueHasBeenAddedToLibrary": "{0} telah ditambah ke media library anda",
"ValueSpecialEpisodeName": "Khas - {0}",
"VersionNumber": "Versi {0}",
"TaskCleanActivityLog": "Log Aktiviti Bersih",
diff --git a/Emby.Server.Implementations/Localization/Core/ne.json b/Emby.Server.Implementations/Localization/Core/ne.json
index 8e820d40c..8584fc065 100644
--- a/Emby.Server.Implementations/Localization/Core/ne.json
+++ b/Emby.Server.Implementations/Localization/Core/ne.json
@@ -69,7 +69,7 @@
"UserDeletedWithName": "प्रयोगकर्ता {0} हटाइएको छ",
"UserCreatedWithName": "प्रयोगकर्ता {0} सिर्जना गरिएको छ",
"User": "प्रयोगकर्ता",
- "PluginInstalledWithName": "",
+ "PluginInstalledWithName": "{0} सभएको थियो",
"StartupEmbyServerIsLoading": "Jellyfin सर्भर लोड हुँदैछ। कृपया छिट्टै फेरि प्रयास गर्नुहोस्।",
"Songs": "गीतहरू",
"Shows": "शोहरू",
diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json
index 79f921bcb..9d512dea1 100644
--- a/Emby.Server.Implementations/Localization/Core/nl.json
+++ b/Emby.Server.Implementations/Localization/Core/nl.json
@@ -15,7 +15,7 @@
"Favorites": "Favorieten",
"Folders": "Mappen",
"Genres": "Genres",
- "HeaderAlbumArtists": "Artiests Album",
+ "HeaderAlbumArtists": "Album Artiesten",
"HeaderContinueWatching": "Kijken hervatten",
"HeaderFavoriteAlbums": "Favoriete albums",
"HeaderFavoriteArtists": "Favoriete artiesten",
diff --git a/Emby.Server.Implementations/Localization/Core/pa.json b/Emby.Server.Implementations/Localization/Core/pa.json
index d1db09232..4ac57b630 100644
--- a/Emby.Server.Implementations/Localization/Core/pa.json
+++ b/Emby.Server.Implementations/Localization/Core/pa.json
@@ -24,7 +24,7 @@
"TasksLibraryCategory": "ਲਾਇਬ੍ਰੇਰੀ",
"TasksMaintenanceCategory": "ਰੱਖ-ਰਖਾਅ",
"VersionNumber": "ਵਰਜਨ {0}",
- "ValueSpecialEpisodeName": "ਵਿਸ਼ੇਸ਼ - {0}",
+ "ValueSpecialEpisodeName": "ਖਾਸ - {0}",
"ValueHasBeenAddedToLibrary": "{0} ਤੁਹਾਡੀ ਮੀਡੀਆ ਲਾਇਬ੍ਰੇਰੀ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ ਹੈ",
"UserStoppedPlayingItemWithValues": "{0} ਨੇ {2} 'ਤੇ {1} ਖੇਡਣਾ ਪੂਰਾ ਕਰ ਲਿਆ ਹੈ",
"UserStartedPlayingItemWithValues": "{0} {2} 'ਤੇ {1} ਖੇਡ ਰਿਹਾ ਹੈ",
@@ -43,8 +43,8 @@
"Sync": "ਸਿੰਕ",
"SubtitleDownloadFailureFromForItem": "ਉਪਸਿਰਲੇਖ {1} ਲਈ {0} ਤੋਂ ਡਾ toਨਲੋਡ ਕਰਨ ਵਿੱਚ ਅਸਫਲ ਰਹੇ",
"StartupEmbyServerIsLoading": "ਜੈਲੀਫਿਨ ਸਰਵਰ ਲੋਡ ਹੋ ਰਿਹਾ ਹੈ. ਕਿਰਪਾ ਕਰਕੇ ਜਲਦੀ ਹੀ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ.",
- "Songs": "ਗਾਣੇ",
- "Shows": "ਸ਼ੋਅਜ਼",
+ "Songs": "ਗਾਣੇਂ",
+ "Shows": "ਸ਼ੋਅ",
"ServerNameNeedsToBeRestarted": "{0} ਮੁੜ ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ",
"ScheduledTaskStartedWithName": "{0} ਸ਼ੁਰੂ ਹੋਇਆ",
"ScheduledTaskFailedWithName": "{0} ਅਸਫਲ",
@@ -53,7 +53,7 @@
"PluginUninstalledWithName": "{0} ਅਣਇੰਸਟੌਲ ਕੀਤਾ ਗਿਆ ਸੀ",
"PluginInstalledWithName": "{0} ਲਗਾਇਆ ਗਿਆ ਸੀ",
"Plugin": "ਪਲੱਗਇਨ",
- "Playlists": "ਪਲੇਲਿਸਟਸ",
+ "Playlists": "ਪਲੇਸੂਚੀਆਂ",
"Photos": "ਫੋਟੋਆਂ",
"NotificationOptionVideoPlaybackStopped": "ਵੀਡੀਓ ਪਲੇਬੈਕ ਰੋਕਿਆ ਗਿਆ",
"NotificationOptionVideoPlayback": "ਵੀਡੀਓ ਪਲੇਬੈਕ ਸ਼ੁਰੂ ਹੋਇਆ",
@@ -102,13 +102,13 @@
"HeaderAlbumArtists": "ਐਲਬਮ ਕਲਾਕਾਰ",
"Genres": "ਸ਼ੈਲੀਆਂ",
"Forced": "ਮਜਬੂਰ",
- "Folders": "ਫੋਲਡਰ",
+ "Folders": "ਫੋਲਡਰਸ",
"Favorites": "ਮਨਪਸੰਦ",
"FailedLoginAttemptWithUserName": "ਤੋਂ ਲਾਗਇਨ ਕੋਸ਼ਿਸ਼ ਫੇਲ ਹੋਈ {0}",
"DeviceOnlineWithName": "{0} ਜੁੜਿਆ ਹੋਇਆ ਹੈ",
"DeviceOfflineWithName": "{0} ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ ਹੈ",
- "Default": "ਮੂਲ",
- "Collections": "ਸੰਗ੍ਰਹਿ",
+ "Default": "ਡਿਫੌਲਟ",
+ "Collections": "ਸੰਗ੍ਰਹਿਣ",
"ChapterNameValue": "ਅਧਿਆਇ {0}",
"Channels": "ਚੈਨਲ",
"CameraImageUploadedFrom": "ਤੋਂ ਇੱਕ ਨਵਾਂ ਕੈਮਰਾ ਚਿੱਤਰ ਅਪਲੋਡ ਕੀਤਾ ਗਿਆ ਹੈ {0}",
diff --git a/Emby.Server.Implementations/Localization/Core/pl.json b/Emby.Server.Implementations/Localization/Core/pl.json
index e8a32a13e..4fa8d2bb4 100644
--- a/Emby.Server.Implementations/Localization/Core/pl.json
+++ b/Emby.Server.Implementations/Localization/Core/pl.json
@@ -15,7 +15,7 @@
"Favorites": "Ulubione",
"Folders": "Foldery",
"Genres": "Gatunki",
- "HeaderAlbumArtists": "Album artysty",
+ "HeaderAlbumArtists": "Wykonawcy albumów",
"HeaderContinueWatching": "Kontynuuj odtwarzanie",
"HeaderFavoriteAlbums": "Ulubione albumy",
"HeaderFavoriteArtists": "Ulubieni wykonawcy",
@@ -47,7 +47,7 @@
"NotificationOptionApplicationUpdateAvailable": "Dostępna aktualizacja aplikacji",
"NotificationOptionApplicationUpdateInstalled": "Zaktualizowano aplikację",
"NotificationOptionAudioPlayback": "Rozpoczęto odtwarzanie muzyki",
- "NotificationOptionAudioPlaybackStopped": "Odtwarzane dźwięku zatrzymane",
+ "NotificationOptionAudioPlaybackStopped": "Odtwarzanie dźwięku zatrzymane",
"NotificationOptionCameraImageUploaded": "Przekazano obraz z urządzenia przenośnego",
"NotificationOptionInstallationFailed": "Nieudana instalacja",
"NotificationOptionNewLibraryContent": "Dodano nową zawartość",
@@ -98,7 +98,7 @@
"TaskRefreshChannels": "Odśwież kanały",
"TaskCleanTranscodeDescription": "Usuwa transkodowane pliki starsze niż 1 dzień.",
"TaskCleanTranscode": "Wyczyść folder transkodowania",
- "TaskUpdatePluginsDescription": "Pobiera i instaluje aktualizacje dla pluginów które są skonfigurowane do automatycznej aktualizacji.",
+ "TaskUpdatePluginsDescription": "Pobiera i instaluje aktualizacje dla pluginów, które są skonfigurowane do automatycznej aktualizacji.",
"TaskUpdatePlugins": "Aktualizuj pluginy",
"TaskRefreshPeopleDescription": "Odświeża metadane o aktorów i reżyserów w Twojej bibliotece mediów.",
"TaskRefreshPeople": "Odśwież obsadę",
diff --git a/Emby.Server.Implementations/Localization/Core/pr.json b/Emby.Server.Implementations/Localization/Core/pr.json
index e3a3bfaf1..81aa996d9 100644
--- a/Emby.Server.Implementations/Localization/Core/pr.json
+++ b/Emby.Server.Implementations/Localization/Core/pr.json
@@ -1,5 +1,7 @@
{
"Books": "Libros",
"AuthenticationSucceededWithUserName": "{0} autentificado correctamente",
- "Artists": "Artistas"
+ "Artists": "Artistas",
+ "Songs": "Shantees",
+ "Albums": "Ships"
}
diff --git a/Emby.Server.Implementations/Localization/Core/pt-PT.json b/Emby.Server.Implementations/Localization/Core/pt-PT.json
index 525a02c88..8870de168 100644
--- a/Emby.Server.Implementations/Localization/Core/pt-PT.json
+++ b/Emby.Server.Implementations/Localization/Core/pt-PT.json
@@ -15,7 +15,7 @@
"Favorites": "Favoritos",
"Folders": "Pastas",
"Genres": "Géneros",
- "HeaderAlbumArtists": "Artistas do Álbum",
+ "HeaderAlbumArtists": "Álbum do Artista",
"HeaderContinueWatching": "Continuar a Ver",
"HeaderFavoriteAlbums": "Álbuns Favoritos",
"HeaderFavoriteArtists": "Artistas Favoritos",
diff --git a/Emby.Server.Implementations/Localization/Core/ro.json b/Emby.Server.Implementations/Localization/Core/ro.json
index 510aac11c..f8fad7b63 100644
--- a/Emby.Server.Implementations/Localization/Core/ro.json
+++ b/Emby.Server.Implementations/Localization/Core/ro.json
@@ -74,7 +74,7 @@
"HeaderFavoriteArtists": "Artiști Favoriți",
"HeaderFavoriteAlbums": "Albume Favorite",
"HeaderContinueWatching": "Vizionează în continuare",
- "HeaderAlbumArtists": "Album Artiști",
+ "HeaderAlbumArtists": "Albume Artiști",
"Genres": "Genuri",
"Folders": "Dosare",
"Favorites": "Favorite",
diff --git a/Emby.Server.Implementations/Localization/Core/sq.json b/Emby.Server.Implementations/Localization/Core/sq.json
index 066ef5587..2766dab06 100644
--- a/Emby.Server.Implementations/Localization/Core/sq.json
+++ b/Emby.Server.Implementations/Localization/Core/sq.json
@@ -74,7 +74,7 @@
"NameSeasonUnknown": "Sezon i panjohur",
"NameSeasonNumber": "Sezoni {0}",
"NameInstallFailed": "Instalimi i {0} dështoi",
- "MusicVideos": "Video muzikore",
+ "MusicVideos": "Video Muzikore",
"Music": "Muzikë",
"Movies": "Filmat",
"MixedContent": "Përmbajtje e përzier",
@@ -96,7 +96,7 @@
"HeaderFavoriteArtists": "Artistët e preferuar",
"HeaderFavoriteAlbums": "Albumet e preferuar",
"HeaderContinueWatching": "Vazhdo të shikosh",
- "HeaderAlbumArtists": "Artistët e Albumeve",
+ "HeaderAlbumArtists": "Artistët e albumeve",
"Genres": "Zhanret",
"Folders": "Skedarët",
"Favorites": "Të preferuarat",
diff --git a/Emby.Server.Implementations/Localization/Core/sr.json b/Emby.Server.Implementations/Localization/Core/sr.json
index 15fb34186..e31208e80 100644
--- a/Emby.Server.Implementations/Localization/Core/sr.json
+++ b/Emby.Server.Implementations/Localization/Core/sr.json
@@ -50,7 +50,7 @@
"NameSeasonUnknown": "Непозната сезона",
"NameSeasonNumber": "Сезона {0}",
"NameInstallFailed": "Инсталација {0} није успела",
- "MusicVideos": "Музички спотови",
+ "MusicVideos": "Музички видео",
"Music": "Музика",
"Movies": "Филмови",
"MixedContent": "Мешовит садржај",
@@ -64,7 +64,7 @@
"ItemRemovedWithName": "{0} уклоњено из библиотеке",
"ItemAddedWithName": "{0} додато у библиотеку",
"Inherit": "Наследи",
- "HomeVideos": "Кућни видео",
+ "HomeVideos": "Кућни Видео",
"HeaderRecordingGroups": "Групе снимања",
"HeaderNextUp": "Следи",
"HeaderLiveTV": "ТВ уживо",
@@ -117,5 +117,6 @@
"TaskCleanActivityLog": "Очисти историју активности",
"Undefined": "Недефинисано",
"Forced": "Принудно",
- "Default": "Подразумевано"
+ "Default": "Подразумевано",
+ "TaskOptimizeDatabase": "Оптимизуј датабазу"
}
diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json
index 6c772c6a2..f3f601661 100644
--- a/Emby.Server.Implementations/Localization/Core/sv.json
+++ b/Emby.Server.Implementations/Localization/Core/sv.json
@@ -15,7 +15,7 @@
"Favorites": "Favoriter",
"Folders": "Mappar",
"Genres": "Genrer",
- "HeaderAlbumArtists": "Artistens album",
+ "HeaderAlbumArtists": "Albumsartister",
"HeaderContinueWatching": "Fortsätt kolla",
"HeaderFavoriteAlbums": "Favoritalbum",
"HeaderFavoriteArtists": "Favoritartister",
@@ -96,8 +96,8 @@
"TaskDownloadMissingSubtitles": "Ladda ned saknade undertexter",
"TaskRefreshChannelsDescription": "Uppdaterar information för internetkanaler.",
"TaskRefreshChannels": "Uppdatera kanaler",
- "TaskCleanTranscodeDescription": "Raderar transkodningsfiler som är mer än en dag gamla.",
- "TaskCleanTranscode": "Töm transkodningskatalog",
+ "TaskCleanTranscodeDescription": "Raderar omkodningsfiler som är mer än en dag gamla.",
+ "TaskCleanTranscode": "Töm omkodningskatalog",
"TaskUpdatePluginsDescription": "Laddar ned och installerar uppdateringar till insticksprogram som är konfigurerade att uppdateras automatiskt.",
"TaskUpdatePlugins": "Uppdatera insticksprogram",
"TaskRefreshPeopleDescription": "Uppdaterar metadata för skådespelare och regissörer i ditt mediabibliotek.",
diff --git a/Emby.Server.Implementations/Localization/Core/ta.json b/Emby.Server.Implementations/Localization/Core/ta.json
index d6e9aa8e5..e3a993625 100644
--- a/Emby.Server.Implementations/Localization/Core/ta.json
+++ b/Emby.Server.Implementations/Localization/Core/ta.json
@@ -85,7 +85,7 @@
"HeaderFavoriteArtists": "பிடித்த கலைஞர்கள்",
"HeaderFavoriteAlbums": "பிடித்த ஆல்பங்கள்",
"HeaderContinueWatching": "தொடர்ந்து பார்",
- "HeaderAlbumArtists": "இசைக் கலைஞர்கள்",
+ "HeaderAlbumArtists": "கலைஞரின் ஆல்பம்",
"Genres": "வகைகள்",
"Favorites": "பிடித்தவை",
"ChapterNameValue": "அத்தியாயம் {0}",
diff --git a/Emby.Server.Implementations/Localization/Core/te.json b/Emby.Server.Implementations/Localization/Core/te.json
index 0967ef424..a9a8ceae0 100644
--- a/Emby.Server.Implementations/Localization/Core/te.json
+++ b/Emby.Server.Implementations/Localization/Core/te.json
@@ -1 +1,23 @@
-{}
+{
+ "ValueSpecialEpisodeName": "ప్రత్యేక - {0}",
+ "Sync": "సమకాలీకరించు",
+ "Songs": "పాటలు",
+ "Shows": "ప్రదర్శనలు",
+ "Playlists": "ప్లేజాబితాలు",
+ "Photos": "ఫోటోలు",
+ "MusicVideos": "మ్యూజిక్ వీడియోలు",
+ "Music": "సంగీతం",
+ "Movies": "సినిమాలు",
+ "HeaderContinueWatching": "చూడటం కొనసాగించండి",
+ "HeaderAlbumArtists": "ఆల్బమ్ కళాకారులు",
+ "Genres": "శైలులు",
+ "Forced": "బలవంతంగా",
+ "Folders": "ఫోల్డర్లు",
+ "Favorites": "ఇష్టమైనవి",
+ "Default": "డిఫాల్ట్",
+ "Collections": "సేకరణలు",
+ "Channels": "ఛానెల్‌లు",
+ "Books": "పుస్తకాలు",
+ "Artists": "కళాకారులు",
+ "Albums": "ఆల్బమ్‌లు"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json
index a5ff3031d..b7ece8d5f 100644
--- a/Emby.Server.Implementations/Localization/Core/vi.json
+++ b/Emby.Server.Implementations/Localization/Core/vi.json
@@ -3,7 +3,7 @@
"Favorites": "Yêu Thích",
"Folders": "Thư Mục",
"Genres": "Thể Loại",
- "HeaderAlbumArtists": "Album Nghệ sĩ",
+ "HeaderAlbumArtists": "Album nghệ sĩ",
"HeaderContinueWatching": "Xem Tiếp",
"HeaderLiveTV": "TV Trực Tiếp",
"Movies": "Phim",
@@ -103,7 +103,7 @@
"HeaderFavoriteEpisodes": "Tập Phim Yêu Thích",
"HeaderFavoriteArtists": "Nghệ Sĩ Yêu Thích",
"HeaderFavoriteAlbums": "Album Ưa Thích",
- "FailedLoginAttemptWithUserName": "Nỗ lực đăng nhập thất bại từ {0}",
+ "FailedLoginAttemptWithUserName": "Đăng nhập không thành công thử từ {0}",
"DeviceOnlineWithName": "{0} đã kết nối",
"DeviceOfflineWithName": "{0} đã ngắt kết nối",
"ChapterNameValue": "Phân Cảnh {0}",
diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json
index f9df62724..ac4eb644b 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-CN.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json
@@ -11,7 +11,7 @@
"Collections": "合集",
"DeviceOfflineWithName": "{0} 已断开",
"DeviceOnlineWithName": "{0} 已连接",
- "FailedLoginAttemptWithUserName": "来自 {0} 的失败登入",
+ "FailedLoginAttemptWithUserName": "从 {0} 尝试登录失败",
"Favorites": "我的最爱",
"Folders": "文件夹",
"Genres": "风格",
diff --git a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs
index 61e18220a..3fa07720a 100644
--- a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs
+++ b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs
@@ -17,7 +17,6 @@ namespace Jellyfin.Api.Helpers
private readonly TranscodingJobDto? _job;
private readonly TranscodingJobHelper? _transcodingJobHelper;
private readonly int _timeoutMs;
- private int _bytesWritten;
private bool _disposed;
/// <summary>
@@ -71,53 +70,58 @@ namespace Jellyfin.Api.Helpers
/// <inheritdoc />
public override void Flush()
{
- _stream.Flush();
+ // Not supported
}
/// <inheritdoc />
public override int Read(byte[] buffer, int offset, int count)
+ => Read(buffer.AsSpan(offset, count));
+
+ /// <inheritdoc />
+ public override int Read(Span<byte> buffer)
{
- return _stream.Read(buffer, offset, count);
+ int totalBytesRead = 0;
+ var stopwatch = Stopwatch.StartNew();
+
+ while (KeepReading(stopwatch.ElapsedMilliseconds))
+ {
+ totalBytesRead += _stream.Read(buffer);
+ if (totalBytesRead > 0)
+ {
+ break;
+ }
+
+ Thread.Sleep(50);
+ }
+
+ UpdateBytesWritten(totalBytesRead);
+
+ return totalBytesRead;
}
/// <inheritdoc />
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ => await ReadAsync(buffer.AsMemory(offset, count), cancellationToken).ConfigureAwait(false);
+
+ /// <inheritdoc />
+ public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
int totalBytesRead = 0;
- int remainingBytesToRead = count;
var stopwatch = Stopwatch.StartNew();
- int newOffset = offset;
- while (remainingBytesToRead > 0)
+ while (KeepReading(stopwatch.ElapsedMilliseconds))
{
- cancellationToken.ThrowIfCancellationRequested();
- int bytesRead = await _stream.ReadAsync(buffer, newOffset, remainingBytesToRead, cancellationToken).ConfigureAwait(false);
-
- remainingBytesToRead -= bytesRead;
- newOffset += bytesRead;
-
- if (bytesRead > 0)
+ totalBytesRead += await _stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
+ if (totalBytesRead > 0)
{
- _bytesWritten += bytesRead;
- totalBytesRead += bytesRead;
-
- if (_job != null)
- {
- _job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten);
- }
+ break;
}
- else
- {
- // If the job is null it's a live stream and will require user action to close, but don't keep it open indefinitely
- if (_job?.HasExited ?? stopwatch.ElapsedMilliseconds > _timeoutMs)
- {
- break;
- }
- await Task.Delay(50, cancellationToken).ConfigureAwait(false);
- }
+ await Task.Delay(50, cancellationToken).ConfigureAwait(false);
}
+ UpdateBytesWritten(totalBytesRead);
+
return totalBytesRead;
}
@@ -159,5 +163,19 @@ namespace Jellyfin.Api.Helpers
base.Dispose(disposing);
}
}
+
+ private void UpdateBytesWritten(int totalBytesRead)
+ {
+ if (_job != null)
+ {
+ _job.BytesDownloaded += totalBytesRead;
+ }
+ }
+
+ private bool KeepReading(long elapsed)
+ {
+ // If the job is null it's a live stream and will require user action to close, but don't keep it open indefinitely
+ return !_job?.HasExited ?? elapsed < _timeoutMs;
+ }
}
}
diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs
index 1b8f24c27..ed071bcd7 100644
--- a/Jellyfin.Api/Helpers/StreamingHelpers.cs
+++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs
@@ -90,6 +90,7 @@ namespace Jellyfin.Api.Helpers
}
var enableDlnaHeaders = !string.IsNullOrWhiteSpace(streamingRequest.Params) ||
+ streamingRequest.StreamOptions.ContainsKey("dlnaheaders") ||
string.Equals(httpRequest.Headers["GetContentFeatures.DLNA.ORG"], "1", StringComparison.OrdinalIgnoreCase);
var state = new StreamState(mediaSourceManager, transcodingJobType, transcodingJobHelper)
diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
index fed837b85..ab67c8732 100644
--- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
+++ b/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
@@ -134,7 +134,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos
/// <summary>
/// Gets or sets bytes downloaded.
/// </summary>
- public long? BytesDownloaded { get; set; }
+ public long BytesDownloaded { get; set; }
/// <summary>
/// Gets or sets bytes transcoded.
diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
index 0136d9f86..7a1ca252c 100644
--- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
+++ b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
@@ -141,7 +141,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos
private bool IsThrottleAllowed(TranscodingJobDto job, int thresholdSeconds)
{
- var bytesDownloaded = job.BytesDownloaded ?? 0;
+ var bytesDownloaded = job.BytesDownloaded;
var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index 6bb8bcdab..9f6d8e7fe 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -22,8 +22,8 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="BDInfo" Version="0.7.6.1" />
- <PackageReference Include="libse" Version="3.6.2" />
+ <PackageReference Include="BDInfo" Version="0.7.6.2" />
+ <PackageReference Include="libse" Version="3.6.4" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
<PackageReference Include="UTF.Unknown" Version="2.5.0" />
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 32ff1dee6..1b3a4fa0f 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -45,6 +45,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
"AC/DC",
"Au/Ra",
+ "Bremer/McCoy",
"이달의 소녀 1/3",
"LOONA 1/3",
"LOONA / yyxy",
diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs
index 90cf8f43b..ef049af4b 100644
--- a/MediaBrowser.Model/Configuration/LibraryOptions.cs
+++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs
@@ -81,6 +81,7 @@ namespace MediaBrowser.Model.Configuration
public bool RequirePerfectSubtitleMatch { get; set; }
public bool SaveSubtitlesWithMedia { get; set; }
+
public bool AutomaticallyAddToCollection { get; set; }
public TypeOptions[] TypeOptions { get; set; }
diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
index e8fd18ae4..58b06ca1d 100644
--- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
+++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
@@ -151,10 +151,12 @@ namespace MediaBrowser.Model.Dlna
DlnaFlags.InteractiveTransferMode |
DlnaFlags.DlnaV15;
- // if (isDirectStream)
- // {
- // flagValue = flagValue | DlnaFlags.ByteBasedSeek;
- // }
+ if (isDirectStream)
+ {
+ flagValue |= DlnaFlags.ByteBasedSeek;
+ }
+
+ // Time based seek is curently disabled when streaming. On LG CX3 adding DlnaFlags.TimeBasedSeek and orgPn causes the DLNA playback to fail (format not supported). Further investigations are needed before enabling the remaining code paths.
// else if (runtimeTicks.HasValue)
// {
// flagValue = flagValue | DlnaFlags.TimeBasedSeek;
@@ -209,6 +211,11 @@ namespace MediaBrowser.Model.Dlna
{
contentFeatureList.Add(orgOp.TrimStart(';') + orgCi + dlnaflags);
}
+ else if (isDirectStream)
+ {
+ // orgOp should be added all the time once the time based seek is resolved for transcoded streams
+ contentFeatureList.Add("DLNA.ORG_PN=" + orgPn + orgOp + orgCi + dlnaflags);
+ }
else
{
contentFeatureList.Add("DLNA.ORG_PN=" + orgPn + orgCi + dlnaflags);
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index 70fef5d66..b1fbe864b 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -31,6 +31,10 @@
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
+ <PackageReference Include="MimeTypes" Version="2.2.1">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
<PackageReference Include="System.Globalization" Version="4.3.0" />
<PackageReference Include="System.Text.Json" Version="6.0.0" />
</ItemGroup>
diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs
index 043cee2a2..506e8e9d6 100644
--- a/MediaBrowser.Model/Net/MimeTypes.cs
+++ b/MediaBrowser.Model/Net/MimeTypes.cs
@@ -12,6 +12,15 @@ namespace MediaBrowser.Model.Net
/// <summary>
/// Class MimeTypes.
/// </summary>
+ ///
+ /// <remarks>
+ /// For more information on MIME types:
+ /// <list type="bullet">
+ /// <item>http://en.wikipedia.org/wiki/Internet_media_type</item>
+ /// <item>https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types</item>
+ /// <item>http://www.iana.org/assignments/media-types/media-types.xhtml</item>
+ /// </list>
+ /// </remarks>
public static class MimeTypes
{
/// <summary>
@@ -50,81 +59,26 @@ namespace MediaBrowser.Model.Net
".wtv",
};
- // http://en.wikipedia.org/wiki/Internet_media_type
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
- // http://www.iana.org/assignments/media-types/media-types.xhtml
- // Add more as needed
+ /// <summary>
+ /// Used for extensions not in <see cref="Model.MimeTypes"/> or to override them.
+ /// </summary>
private static readonly Dictionary<string, string> _mimeTypeLookup = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
// Type application
- { ".7z", "application/x-7z-compressed" },
- { ".azw", "application/vnd.amazon.ebook" },
{ ".azw3", "application/vnd.amazon.ebook" },
- { ".cbz", "application/x-cbz" },
- { ".cbr", "application/epub+zip" },
- { ".eot", "application/vnd.ms-fontobject" },
- { ".epub", "application/epub+zip" },
- { ".js", "application/x-javascript" },
- { ".json", "application/json" },
- { ".m3u8", "application/x-mpegURL" },
- { ".map", "application/x-javascript" },
- { ".mobi", "application/x-mobipocket-ebook" },
- { ".opf", "application/oebps-package+xml" },
- { ".pdf", "application/pdf" },
- { ".rar", "application/vnd.rar" },
- { ".srt", "application/x-subrip" },
- { ".ttml", "application/ttml+xml" },
- { ".wasm", "application/wasm" },
- { ".xml", "application/xml" },
- { ".zip", "application/zip" },
// Type image
- { ".bmp", "image/bmp" },
- { ".gif", "image/gif" },
- { ".ico", "image/vnd.microsoft.icon" },
- { ".jpg", "image/jpeg" },
- { ".jpeg", "image/jpeg" },
- { ".png", "image/png" },
- { ".svg", "image/svg+xml" },
- { ".svgz", "image/svg+xml" },
{ ".tbn", "image/jpeg" },
- { ".tif", "image/tiff" },
- { ".tiff", "image/tiff" },
- { ".webp", "image/webp" },
-
- // Type font
- { ".ttf", "font/ttf" },
- { ".woff", "font/woff" },
- { ".woff2", "font/woff2" },
// Type text
{ ".ass", "text/x-ssa" },
{ ".ssa", "text/x-ssa" },
- { ".css", "text/css" },
- { ".csv", "text/csv" },
{ ".edl", "text/plain" },
- { ".rtf", "text/rtf" },
- { ".txt", "text/plain" },
- { ".vtt", "text/vtt" },
+ { ".html", "text/html; charset=UTF-8" },
+ { ".htm", "text/html; charset=UTF-8" },
// Type video
- { ".3gp", "video/3gpp" },
- { ".3g2", "video/3gpp2" },
- { ".asf", "video/x-ms-asf" },
- { ".avi", "video/x-msvideo" },
- { ".flv", "video/x-flv" },
- { ".mp4", "video/mp4" },
- { ".m4s", "video/mp4" },
- { ".m4v", "video/x-m4v" },
{ ".mpegts", "video/mp2t" },
- { ".mpg", "video/mpeg" },
- { ".mkv", "video/x-matroska" },
- { ".mov", "video/quicktime" },
- { ".mpd", "video/vnd.mpeg.dash.mpd" },
- { ".ogv", "video/ogg" },
- { ".ts", "video/mp2t" },
- { ".webm", "video/webm" },
- { ".wmv", "video/x-ms-wmv" },
// Type audio
{ ".aac", "audio/aac" },
@@ -133,37 +87,47 @@ namespace MediaBrowser.Model.Net
{ ".dsf", "audio/dsf" },
{ ".dsp", "audio/dsp" },
{ ".flac", "audio/flac" },
- { ".m4a", "audio/mp4" },
{ ".m4b", "audio/m4b" },
- { ".mid", "audio/midi" },
- { ".midi", "audio/midi" },
{ ".mp3", "audio/mpeg" },
- { ".oga", "audio/ogg" },
- { ".ogg", "audio/ogg" },
- { ".opus", "audio/ogg" },
{ ".vorbis", "audio/vorbis" },
- { ".wav", "audio/wav" },
{ ".webma", "audio/webm" },
- { ".wma", "audio/x-ms-wma" },
{ ".wv", "audio/x-wavpack" },
{ ".xsp", "audio/xsp" },
};
- private static readonly Dictionary<string, string> _extensionLookup = CreateExtensionLookup();
-
- private static Dictionary<string, string> CreateExtensionLookup()
+ private static readonly Dictionary<string, string> _extensionLookup = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
- var dict = _mimeTypeLookup
- .GroupBy(i => i.Value)
- .ToDictionary(x => x.Key, x => x.First().Key, StringComparer.OrdinalIgnoreCase);
+ // Type application
+ { "application/x-cbz", ".cbz" },
+ { "application/x-javascript", ".js" },
+ { "application/xml", ".xml" },
+ { "application/x-mpegURL", ".m3u8" },
- dict["image/jpg"] = ".jpg";
- dict["image/x-png"] = ".png";
+ // Type audio
+ { "audio/aac", ".aac" },
+ { "audio/ac3", ".ac3" },
+ { "audio/dsf", ".dsf" },
+ { "audio/dsp", ".dsp" },
+ { "audio/flac", ".flac" },
+ { "audio/m4b", ".m4b" },
+ { "audio/vorbis", ".vorbis" },
+ { "audio/x-ape", ".ape" },
+ { "audio/xsp", ".xsp" },
+ { "audio/x-wavpack", ".wv" },
- dict["audio/x-aac"] = ".aac";
+ // Type image
+ { "image/jpg", ".jpg" },
+ { "image/x-png", ".png" },
- return dict;
- }
+ // Type text
+ { "text/plain", ".txt" },
+ { "text/rtf", ".rtf" },
+ { "text/x-ssa", ".ssa" },
+
+ // Type video
+ { "video/vnd.mpeg.dash.mpd", ".mpd" },
+ { "video/x-matroska", ".mkv" },
+ };
public static string GetMimeType(string path) => GetMimeType(path, "application/octet-stream");
@@ -188,29 +152,15 @@ namespace MediaBrowser.Model.Net
return result;
}
- // Catch-all for all video types that don't require specific mime types
- if (_videoFileExtensions.Contains(ext))
- {
- return string.Concat("video/", ext.AsSpan(1));
- }
-
- // Type text
- if (string.Equals(ext, ".html", StringComparison.OrdinalIgnoreCase)
- || string.Equals(ext, ".htm", StringComparison.OrdinalIgnoreCase))
+ if (Model.MimeTypes.TryGetMimeType(filename, out var mimeType))
{
- return "text/html; charset=UTF-8";
+ return mimeType;
}
- if (string.Equals(ext, ".log", StringComparison.OrdinalIgnoreCase)
- || string.Equals(ext, ".srt", StringComparison.OrdinalIgnoreCase))
- {
- return "text/plain";
- }
-
- // Misc
- if (string.Equals(ext, ".dll", StringComparison.OrdinalIgnoreCase))
+ // Catch-all for all video types that don't require specific mime types
+ if (_videoFileExtensions.Contains(ext))
{
- return "application/octet-stream";
+ return string.Concat("video/", ext.AsSpan(1));
}
return defaultValue;
@@ -231,7 +181,8 @@ namespace MediaBrowser.Model.Net
return result;
}
- return null;
+ var extension = Model.MimeTypes.GetMimeTypeExtensions(mimeType).FirstOrDefault();
+ return string.IsNullOrEmpty(extension) ? null : "." + extension;
}
}
}
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
index d4b5d8655..887a7f80c 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
@@ -38,17 +38,9 @@ namespace MediaBrowser.Providers.MediaInfo
IHasItemChangeMonitor
{
private readonly ILogger<FFProbeProvider> _logger;
- private readonly IMediaEncoder _mediaEncoder;
- private readonly IItemRepository _itemRepo;
- private readonly IBlurayExaminer _blurayExaminer;
- private readonly ILocalizationManager _localization;
- private readonly IEncodingManager _encodingManager;
- private readonly IServerConfigurationManager _config;
- private readonly ISubtitleManager _subtitleManager;
- private readonly IChapterManager _chapterManager;
- private readonly ILibraryManager _libraryManager;
- private readonly IMediaSourceManager _mediaSourceManager;
private readonly SubtitleResolver _subtitleResolver;
+ private readonly FFProbeVideoInfo _videoProber;
+ private readonly FFProbeAudioInfo _audioProber;
private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None);
@@ -66,18 +58,21 @@ namespace MediaBrowser.Providers.MediaInfo
ILibraryManager libraryManager)
{
_logger = logger;
- _mediaEncoder = mediaEncoder;
- _itemRepo = itemRepo;
- _blurayExaminer = blurayExaminer;
- _localization = localization;
- _encodingManager = encodingManager;
- _config = config;
- _subtitleManager = subtitleManager;
- _chapterManager = chapterManager;
- _libraryManager = libraryManager;
- _mediaSourceManager = mediaSourceManager;
_subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager);
+ _videoProber = new FFProbeVideoInfo(
+ _logger,
+ mediaSourceManager,
+ mediaEncoder,
+ itemRepo,
+ blurayExaminer,
+ localization,
+ encodingManager,
+ config,
+ subtitleManager,
+ chapterManager,
+ libraryManager);
+ _audioProber = new FFProbeAudioInfo(mediaSourceManager, mediaEncoder, itemRepo, libraryManager);
}
public string Name => "ffprobe";
@@ -177,20 +172,7 @@ namespace MediaBrowser.Providers.MediaInfo
FetchShortcutInfo(item);
}
- var prober = new FFProbeVideoInfo(
- _logger,
- _mediaSourceManager,
- _mediaEncoder,
- _itemRepo,
- _blurayExaminer,
- _localization,
- _encodingManager,
- _config,
- _subtitleManager,
- _chapterManager,
- _libraryManager);
-
- return prober.ProbeVideo(item, options, cancellationToken);
+ return _videoProber.ProbeVideo(item, options, cancellationToken);
}
private string NormalizeStrmLine(string line)
@@ -226,9 +208,7 @@ namespace MediaBrowser.Providers.MediaInfo
FetchShortcutInfo(item);
}
- var prober = new FFProbeAudioInfo(_mediaSourceManager, _mediaEncoder, _itemRepo, _libraryManager);
-
- return prober.Probe(item, options, cancellationToken);
+ return _audioProber.Probe(item, options, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
index dd4a5f061..ba284187e 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
@@ -1,7 +1,3 @@
-#nullable disable
-
-#pragma warning disable CA1002, CS1591
-
using System;
using System.Collections.Generic;
using System.IO;
@@ -12,15 +8,30 @@ using MediaBrowser.Model.Globalization;
namespace MediaBrowser.Providers.MediaInfo
{
+ /// <summary>
+ /// Resolves external subtitles for videos.
+ /// </summary>
public class SubtitleResolver
{
private readonly ILocalizationManager _localization;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SubtitleResolver"/> class.
+ /// </summary>
+ /// <param name="localization">The localization manager.</param>
public SubtitleResolver(ILocalizationManager localization)
{
_localization = localization;
}
+ /// <summary>
+ /// Retrieves the external subtitle streams for the provided video.
+ /// </summary>
+ /// <param name="video">The video to search from.</param>
+ /// <param name="startIndex">The stream index to start adding subtitle streams at.</param>
+ /// <param name="directoryService">The directory service to search for files.</param>
+ /// <param name="clearCache">True if the directory service cache should be cleared before searching.</param>
+ /// <returns>The external subtitle streams located.</returns>
public List<MediaStream> GetExternalSubtitleStreams(
Video video,
int startIndex,
@@ -56,6 +67,13 @@ namespace MediaBrowser.Providers.MediaInfo
return streams;
}
+ /// <summary>
+ /// Locates the external subtitle files for the provided video.
+ /// </summary>
+ /// <param name="video">The video to search from.</param>
+ /// <param name="directoryService">The directory service to search for files.</param>
+ /// <param name="clearCache">True if the directory service cache should be cleared before searching.</param>
+ /// <returns>The external subtitle file paths located.</returns>
public IEnumerable<string> GetExternalSubtitleFiles(
Video video,
IDirectoryService directoryService,
@@ -74,6 +92,13 @@ namespace MediaBrowser.Providers.MediaInfo
}
}
+ /// <summary>
+ /// Extracts the subtitle files from the provided list and adds them to the list of streams.
+ /// </summary>
+ /// <param name="streams">The list of streams to add external subtitles to.</param>
+ /// <param name="videoPath">The path to the video file.</param>
+ /// <param name="startIndex">The stream index to start adding subtitle streams at.</param>
+ /// <param name="files">The files to add if they are subtitles.</param>
public void AddExternalSubtitleStreams(
List<MediaStream> streams,
string videoPath,
@@ -120,6 +145,12 @@ namespace MediaBrowser.Providers.MediaInfo
while (languageSpan.Length > 0)
{
var lastDot = languageSpan.LastIndexOf('.');
+ if (lastDot < videoFileNameWithoutExtension.Length)
+ {
+ languageSpan = ReadOnlySpan<char>.Empty;
+ break;
+ }
+
var currentSlice = languageSpan[lastDot..];
if (currentSlice.Equals(".default", StringComparison.OrdinalIgnoreCase)
|| currentSlice.Equals(".forced", StringComparison.OrdinalIgnoreCase)
@@ -133,12 +164,19 @@ namespace MediaBrowser.Providers.MediaInfo
break;
}
- // Try to translate to three character code
- // Be flexible and check against both the full and three character versions
var language = languageSpan.ToString();
- var culture = _localization.FindLanguageInfo(language);
+ if (string.IsNullOrWhiteSpace(language))
+ {
+ language = null;
+ }
+ else
+ {
+ // Try to translate to three character code
+ // Be flexible and check against both the full and three character versions
+ var culture = _localization.FindLanguageInfo(language);
- language = culture == null ? language : culture.ThreeLetterISOLanguageName;
+ language = culture == null ? language : culture.ThreeLetterISOLanguageName;
+ }
mediaStream = new MediaStream
{
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs b/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs
new file mode 100644
index 000000000..0bab7c3ca
--- /dev/null
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs
@@ -0,0 +1,41 @@
+using System.Net.Mime;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using TMDbLib.Objects.General;
+
+namespace MediaBrowser.Providers.Plugins.Tmdb.Api
+{
+ /// <summary>
+ /// The TMDb api controller.
+ /// </summary>
+ [ApiController]
+ [Authorize(Policy = "DefaultAuthorization")]
+ [Route("[controller]")]
+ [Produces(MediaTypeNames.Application.Json)]
+ public class TmdbController : ControllerBase
+ {
+ private readonly TmdbClientManager _tmdbClientManager;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TmdbController"/> class.
+ /// </summary>
+ /// <param name="tmdbClientManager">The TMDb client manager.</param>
+ public TmdbController(TmdbClientManager tmdbClientManager)
+ {
+ _tmdbClientManager = tmdbClientManager;
+ }
+
+ /// <summary>
+ /// Gets the TMDb image configuration options.
+ /// </summary>
+ /// <returns>The image portion of the TMDb client configuration.</returns>
+ [HttpGet("ClientConfiguration")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task<ConfigImageTypes> TmdbClientConfiguration()
+ {
+ return (await _tmdbClientManager.GetClientConfiguration().ConfigureAwait(false)).Images;
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs
index 9a78a7536..dec796148 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs
@@ -26,5 +26,25 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// Gets or sets a value indicating the maximum number of cast members to fetch for an item.
/// </summary>
public int MaxCastMembers { get; set; } = 15;
+
+ /// <summary>
+ /// Gets or sets a value indicating the poster image size to fetch.
+ /// </summary>
+ public string? PosterSize { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating the backdrop image size to fetch.
+ /// </summary>
+ public string? BackdropSize { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating the profile image size to fetch.
+ /// </summary>
+ public string? ProfileSize { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating the still image size to fetch.
+ /// </summary>
+ public string? StillSize { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html
index 12b4c7ca4..52693795b 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html
@@ -24,7 +24,21 @@
<input is="emby-input" type="number" id="maxCastMembers" pattern="[0-9]*" required min="0" max="1000" label="Max Cast Members" />
<div class="fieldDescription">The maximum number of cast members to fetch for an item.</div>
</div>
- <br />
+ <div class="verticalSection verticalSection-extrabottompadding">
+ <h2>Image Scaling</h2>
+ <div class="selectContainer">
+ <select is="emby-select" id="selectPosterSize" label="Poster"></select>
+ </div>
+ <div class="selectContainer">
+ <select is="emby-select" id="selectBackdropSize" label="Backdrop"></select>
+ </div>
+ <div class="selectContainer">
+ <select is="emby-select" id="selectProfileSize" label="Profile"></select>
+ </div>
+ <div class="selectContainer">
+ <select is="emby-select" id="selectStillSize" label="Still"></select>
+ </div>
+ </div>
<div>
<button is="emby-button" type="submit" class="raised button-submit block"><span>Save</span></button>
</div>
@@ -39,6 +53,47 @@
document.querySelector('.configPage')
.addEventListener('pageshow', function () {
Dashboard.showLoadingMsg();
+
+ var clientConfig, pluginConfig;
+ var configureImageScaling = function() {
+ if (clientConfig === null || pluginConfig === null) {
+ return;
+ }
+
+ var sizeOptionsGenerator = function (size) {
+ return '<option value="' + size + '">' + size + '</option>';
+ }
+
+ var selPosterSize = document.querySelector('#selectPosterSize');
+ selPosterSize.innerHTML = clientConfig.PosterSizes.map(sizeOptionsGenerator);
+ selPosterSize.value = pluginConfig.PosterSize;
+
+ var selBackdropSize = document.querySelector('#selectBackdropSize');
+ selBackdropSize.innerHTML = clientConfig.BackdropSizes.map(sizeOptionsGenerator);
+ selBackdropSize.value = pluginConfig.BackdropSize;
+
+ var selProfileSize = document.querySelector('#selectProfileSize');
+ selProfileSize.innerHTML = clientConfig.ProfileSizes.map(sizeOptionsGenerator);
+ selProfileSize.value = pluginConfig.ProfileSize;
+
+ var selStillSize = document.querySelector('#selectStillSize');
+ selStillSize.innerHTML = clientConfig.StillSizes.map(sizeOptionsGenerator);
+ selStillSize.value = pluginConfig.StillSize;
+
+ Dashboard.hideLoadingMsg();
+ }
+
+ const request = {
+ url: ApiClient.getUrl('tmdb/ClientConfiguration'),
+ dataType: 'json',
+ type: 'GET',
+ headers: { accept: 'application/json' }
+ }
+ ApiClient.fetch(request).then(function (config) {
+ clientConfig = config;
+ configureImageScaling();
+ });
+
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
document.querySelector('#includeAdult').checked = config.IncludeAdult;
document.querySelector('#excludeTagsSeries').checked = config.ExcludeTagsSeries;
@@ -51,7 +106,8 @@
cancelable: false
}));
- Dashboard.hideLoadingMsg();
+ pluginConfig = config;
+ configureImageScaling();
});
});
@@ -65,6 +121,10 @@
config.ExcludeTagsSeries = document.querySelector('#excludeTagsSeries').checked;
config.ExcludeTagsMovies = document.querySelector('#excludeTagsMovies').checked;
config.MaxCastMembers = document.querySelector('#maxCastMembers').value;
+ config.PosterSize = document.querySelector('#selectPosterSize').value;
+ config.BackdropSize = document.querySelector('#selectBackdropSize').value;
+ config.ProfileSize = document.querySelector('#selectProfileSize').value;
+ config.StillSize = document.querySelector('#selectStillSize').value;
ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
});
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
index cb644c8ca..28d6f4d0c 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
@@ -498,7 +498,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
return null;
}
- return _tmDbClient.GetImageUrl(size, path).ToString();
+ return _tmDbClient.GetImageUrl(size, path, true).ToString();
}
/// <summary>
@@ -508,7 +508,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <returns>The absolute URL.</returns>
public string GetPosterUrl(string posterPath)
{
- return GetUrl(_tmDbClient.Config.Images.PosterSizes[^1], posterPath);
+ return GetUrl(Plugin.Instance.Configuration.PosterSize, posterPath);
}
/// <summary>
@@ -518,7 +518,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <returns>The absolute URL.</returns>
public string GetProfileUrl(string actorProfilePath)
{
- return GetUrl(_tmDbClient.Config.Images.ProfileSizes[^1], actorProfilePath);
+ return GetUrl(Plugin.Instance.Configuration.ProfileSize, actorProfilePath);
}
/// <summary>
@@ -529,7 +529,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="results">The collection to add the remote images into.</param>
public void ConvertPostersToRemoteImageInfo(List<ImageData> images, string requestLanguage, List<RemoteImageInfo> results)
{
- ConvertToRemoteImageInfo(images, _tmDbClient.Config.Images.PosterSizes[^1], ImageType.Primary, requestLanguage, results);
+ ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.PosterSize, ImageType.Primary, requestLanguage, results);
}
/// <summary>
@@ -540,7 +540,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="results">The collection to add the remote images into.</param>
public void ConvertBackdropsToRemoteImageInfo(List<ImageData> images, string requestLanguage, List<RemoteImageInfo> results)
{
- ConvertToRemoteImageInfo(images, _tmDbClient.Config.Images.BackdropSizes[^1], ImageType.Backdrop, requestLanguage, results);
+ ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.BackdropSize, ImageType.Backdrop, requestLanguage, results);
}
/// <summary>
@@ -551,7 +551,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="results">The collection to add the remote images into.</param>
public void ConvertProfilesToRemoteImageInfo(List<ImageData> images, string requestLanguage, List<RemoteImageInfo> results)
{
- ConvertToRemoteImageInfo(images, _tmDbClient.Config.Images.ProfileSizes[^1], ImageType.Primary, requestLanguage, results);
+ ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.ProfileSize, ImageType.Primary, requestLanguage, results);
}
/// <summary>
@@ -562,7 +562,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="results">The collection to add the remote images into.</param>
public void ConvertStillsToRemoteImageInfo(List<ImageData> images, string requestLanguage, List<RemoteImageInfo> results)
{
- ConvertToRemoteImageInfo(images, _tmDbClient.Config.Images.StillSizes[^1], ImageType.Primary, requestLanguage, results);
+ ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.StillSize, ImageType.Primary, requestLanguage, results);
}
/// <summary>
@@ -575,16 +575,20 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="results">The collection to add the remote images into.</param>
private void ConvertToRemoteImageInfo(List<ImageData> images, string size, ImageType type, string requestLanguage, List<RemoteImageInfo> results)
{
+ // sizes provided are for original resolution, don't store them when downloading scaled images
+ var scaleImage = !string.Equals(size, "original", StringComparison.OrdinalIgnoreCase);
+
for (var i = 0; i < images.Count; i++)
{
var image = images[i];
+
results.Add(new RemoteImageInfo
{
Url = GetUrl(size, image.FilePath),
CommunityRating = image.VoteAverage,
VoteCount = image.VoteCount,
- Width = image.Width,
- Height = image.Height,
+ Width = scaleImage ? null : image.Width,
+ Height = scaleImage ? null : image.Height,
Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, requestLanguage),
ProviderName = TmdbUtils.ProviderName,
Type = type,
@@ -593,9 +597,51 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
}
}
- private Task EnsureClientConfigAsync()
+ private async Task EnsureClientConfigAsync()
+ {
+ if (!_tmDbClient.HasConfig)
+ {
+ var config = await _tmDbClient.GetConfigAsync().ConfigureAwait(false);
+ ValidatePreferences(config);
+ }
+ }
+
+ private static void ValidatePreferences(TMDbConfig config)
{
- return !_tmDbClient.HasConfig ? _tmDbClient.GetConfigAsync() : Task.CompletedTask;
+ var imageConfig = config.Images;
+
+ var pluginConfig = Plugin.Instance.Configuration;
+
+ if (!imageConfig.PosterSizes.Contains(pluginConfig.PosterSize))
+ {
+ pluginConfig.PosterSize = imageConfig.PosterSizes[^1];
+ }
+
+ if (!imageConfig.BackdropSizes.Contains(pluginConfig.BackdropSize))
+ {
+ pluginConfig.BackdropSize = imageConfig.BackdropSizes[^1];
+ }
+
+ if (!imageConfig.ProfileSizes.Contains(pluginConfig.ProfileSize))
+ {
+ pluginConfig.ProfileSize = imageConfig.ProfileSizes[^1];
+ }
+
+ if (!imageConfig.StillSizes.Contains(pluginConfig.StillSize))
+ {
+ pluginConfig.StillSize = imageConfig.StillSizes[^1];
+ }
+ }
+
+ /// <summary>
+ /// Gets the <see cref="TMDbClient"/> configuration.
+ /// </summary>
+ /// <returns>The configuration.</returns>
+ public async Task<TMDbConfig> GetClientConfiguration()
+ {
+ await EnsureClientConfigAsync().ConfigureAwait(false);
+
+ return _tmDbClient.Config;
}
/// <inheritdoc />
diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
index 9d223b4b6..770dc3e00 100644
--- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs
+++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
@@ -130,11 +130,12 @@ namespace MediaBrowser.Providers.TV
/// <returns>The async task.</returns>
private async Task FillInMissingSeasonsAsync(Series series, CancellationToken cancellationToken)
{
- var episodesInSeriesFolder = series.GetRecursiveChildren(i => i is Episode)
- .Cast<Episode>()
+ var seriesChildren = series.GetRecursiveChildren(i => i is Episode || i is Season);
+ var episodesInSeriesFolder = seriesChildren
+ .OfType<Episode>()
.Where(i => !i.IsInSeasonFolder);
- List<Season> seasons = series.Children.OfType<Season>().ToList();
+ List<Season> seasons = seriesChildren.OfType<Season>().ToList();
// Loop through the unique season numbers
foreach (var episode in episodesInSeriesFolder)
diff --git a/debian/jellyfin.service b/debian/jellyfin.service
index e215a8536..071f949dd 100644
--- a/debian/jellyfin.service
+++ b/debian/jellyfin.service
@@ -13,7 +13,20 @@ TimeoutSec = 15
NoNewPrivileges=true
SystemCallArchitectures=native
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
-ProtectKernelModules=True
+RestrictNamespaces=true
+RestrictRealtime=true
+RestrictSUIDSGID=true
+ProtectClock=true
+ProtectControlGroups=true
+ProtectHostname=true
+ProtectKernelLogs=true
+ProtectKernelModules=true
+ProtectKernelTunables=true
+LockPersonality=true
+PrivateTmp=true
+PrivateDevices=false
+PrivateUsers=true
+RemoveIPC=true
SystemCallFilter=~@clock
SystemCallFilter=~@aio
SystemCallFilter=~@chown
diff --git a/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs b/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs
new file mode 100644
index 000000000..55050cc95
--- /dev/null
+++ b/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs
@@ -0,0 +1,164 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Net;
+using Xunit;
+
+namespace Jellyfin.Model.Tests.Net
+{
+ public class MimeTypesTests
+ {
+ [Theory]
+ [InlineData(".dll", "application/octet-stream")]
+ [InlineData(".log", "text/plain")]
+ [InlineData(".srt", "application/x-subrip")]
+ [InlineData(".html", "text/html; charset=UTF-8")]
+ [InlineData(".htm", "text/html; charset=UTF-8")]
+ [InlineData(".7z", "application/x-7z-compressed")]
+ [InlineData(".azw", "application/vnd.amazon.ebook")]
+ [InlineData(".azw3", "application/vnd.amazon.ebook")]
+ [InlineData(".eot", "application/vnd.ms-fontobject")]
+ [InlineData(".epub", "application/epub+zip")]
+ [InlineData(".json", "application/json")]
+ [InlineData(".mobi", "application/x-mobipocket-ebook")]
+ [InlineData(".opf", "application/oebps-package+xml")]
+ [InlineData(".pdf", "application/pdf")]
+ [InlineData(".rar", "application/vnd.rar")]
+ [InlineData(".ttml", "application/ttml+xml")]
+ [InlineData(".wasm", "application/wasm")]
+ [InlineData(".xml", "application/xml")]
+ [InlineData(".zip", "application/zip")]
+ [InlineData(".bmp", "image/bmp")]
+ [InlineData(".gif", "image/gif")]
+ [InlineData(".ico", "image/vnd.microsoft.icon")]
+ [InlineData(".jpg", "image/jpeg")]
+ [InlineData(".jpeg", "image/jpeg")]
+ [InlineData(".png", "image/png")]
+ [InlineData(".svg", "image/svg+xml")]
+ [InlineData(".svgz", "image/svg+xml")]
+ [InlineData(".tbn", "image/jpeg")]
+ [InlineData(".tif", "image/tiff")]
+ [InlineData(".tiff", "image/tiff")]
+ [InlineData(".webp", "image/webp")]
+ [InlineData(".ttf", "font/ttf")]
+ [InlineData(".woff", "font/woff")]
+ [InlineData(".woff2", "font/woff2")]
+ [InlineData(".ass", "text/x-ssa")]
+ [InlineData(".ssa", "text/x-ssa")]
+ [InlineData(".css", "text/css")]
+ [InlineData(".csv", "text/csv")]
+ [InlineData(".edl", "text/plain")]
+ [InlineData(".txt", "text/plain")]
+ [InlineData(".vtt", "text/vtt")]
+ [InlineData(".3gp", "video/3gpp")]
+ [InlineData(".3g2", "video/3gpp2")]
+ [InlineData(".asf", "video/x-ms-asf")]
+ [InlineData(".avi", "video/x-msvideo")]
+ [InlineData(".flv", "video/x-flv")]
+ [InlineData(".mp4", "video/mp4")]
+ [InlineData(".m4v", "video/x-m4v")]
+ [InlineData(".mpegts", "video/mp2t")]
+ [InlineData(".mpg", "video/mpeg")]
+ [InlineData(".mkv", "video/x-matroska")]
+ [InlineData(".mov", "video/quicktime")]
+ [InlineData(".ogv", "video/ogg")]
+ [InlineData(".ts", "video/mp2t")]
+ [InlineData(".webm", "video/webm")]
+ [InlineData(".wmv", "video/x-ms-wmv")]
+ [InlineData(".aac", "audio/aac")]
+ [InlineData(".ac3", "audio/ac3")]
+ [InlineData(".ape", "audio/x-ape")]
+ [InlineData(".dsf", "audio/dsf")]
+ [InlineData(".dsp", "audio/dsp")]
+ [InlineData(".flac", "audio/flac")]
+ [InlineData(".m4a", "audio/mp4")]
+ [InlineData(".m4b", "audio/m4b")]
+ [InlineData(".mid", "audio/midi")]
+ [InlineData(".midi", "audio/midi")]
+ [InlineData(".mp3", "audio/mpeg")]
+ [InlineData(".oga", "audio/ogg")]
+ [InlineData(".ogg", "audio/ogg")]
+ [InlineData(".opus", "audio/ogg")]
+ [InlineData(".vorbis", "audio/vorbis")]
+ [InlineData(".wav", "audio/wav")]
+ [InlineData(".webma", "audio/webm")]
+ [InlineData(".wma", "audio/x-ms-wma")]
+ [InlineData(".wv", "audio/x-wavpack")]
+ [InlineData(".xsp", "audio/xsp")]
+ public void GetMimeType_Valid_ReturnsCorrectResult(string input, string expectedResult)
+ {
+ Assert.Equal(expectedResult, MimeTypes.GetMimeType(input, null));
+ }
+
+ [Theory]
+ [InlineData("application/epub+zip", ".epub")]
+ [InlineData("application/json", ".json")]
+ [InlineData("application/oebps-package+xml", ".opf")]
+ [InlineData("application/pdf", ".pdf")]
+ [InlineData("application/ttml+xml", ".ttml")]
+ [InlineData("application/vnd.amazon.ebook", ".azw")]
+ [InlineData("application/vnd.ms-fontobject", ".eot")]
+ [InlineData("application/vnd.rar", ".rar")]
+ [InlineData("application/wasm", ".wasm")]
+ [InlineData("application/x-7z-compressed", ".7z")]
+ [InlineData("application/x-cbz", ".cbz")]
+ [InlineData("application/x-javascript", ".js")]
+ [InlineData("application/x-mobipocket-ebook", ".mobi")]
+ [InlineData("application/x-mpegURL", ".m3u8")]
+ [InlineData("application/x-subrip", ".srt")]
+ [InlineData("application/xml", ".xml")]
+ [InlineData("application/zip", ".zip")]
+ [InlineData("audio/aac", ".aac")]
+ [InlineData("audio/ac3", ".ac3")]
+ [InlineData("audio/dsf", ".dsf")]
+ [InlineData("audio/dsp", ".dsp")]
+ [InlineData("audio/flac", ".flac")]
+ [InlineData("audio/m4b", ".m4b")]
+ [InlineData("audio/mp4", ".m4a")]
+ [InlineData("audio/vorbis", ".vorbis")]
+ [InlineData("audio/wav", ".wav")]
+ [InlineData("audio/x-aac", ".aac")]
+ [InlineData("audio/x-ape", ".ape")]
+ [InlineData("audio/x-ms-wma", ".wma")]
+ [InlineData("audio/x-wavpack", ".wv")]
+ [InlineData("audio/xsp", ".xsp")]
+ [InlineData("font/ttf", ".ttf")]
+ [InlineData("font/woff", ".woff")]
+ [InlineData("font/woff2", ".woff2")]
+ [InlineData("image/bmp", ".bmp")]
+ [InlineData("image/gif", ".gif")]
+ [InlineData("image/jpg", ".jpg")]
+ [InlineData("image/png", ".png")]
+ [InlineData("image/svg+xml", ".svg")]
+ [InlineData("image/tiff", ".tif")]
+ [InlineData("image/vnd.microsoft.icon", ".ico")]
+ [InlineData("image/webp", ".webp")]
+ [InlineData("image/x-png", ".png")]
+ [InlineData("text/css", ".css")]
+ [InlineData("text/csv", ".csv")]
+ [InlineData("text/plain", ".txt")]
+ [InlineData("text/rtf", ".rtf")]
+ [InlineData("text/vtt", ".vtt")]
+ [InlineData("text/x-ssa", ".ssa")]
+ [InlineData("video/3gpp", ".3gp")]
+ [InlineData("video/3gpp2", ".3g2")]
+ [InlineData("video/mp2t", ".ts")]
+ [InlineData("video/mp4", ".mp4")]
+ [InlineData("video/ogg", ".ogv")]
+ [InlineData("video/quicktime", ".mov")]
+ [InlineData("video/vnd.mpeg.dash.mpd", ".mpd")]
+ [InlineData("video/webm", ".webm")]
+ [InlineData("video/x-flv", ".flv")]
+ [InlineData("video/x-m4v", ".m4v")]
+ [InlineData("video/x-matroska", ".mkv")]
+ [InlineData("video/x-ms-asf", ".asf")]
+ [InlineData("video/x-ms-wmv", ".wmv")]
+ [InlineData("video/x-msvideo", ".avi")]
+ public void ToExtension_Valid_ReturnsCorrectResult(string input, string expectedResult)
+ {
+ Assert.Equal(expectedResult, MimeTypes.ToExtension(input));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs
index c289a7112..33da277e3 100644
--- a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs
+++ b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs
@@ -80,6 +80,37 @@ namespace Jellyfin.Providers.Tests.MediaInfo
}
}
+ [Theory]
+ [InlineData("/video/My Video.mkv", "/video/My Video.srt", "srt", null, false, false)]
+ [InlineData("/video/My.Video.mkv", "/video/My.Video.srt", "srt", null, false, false)]
+ [InlineData("/video/My.Video.mkv", "/video/My.Video.foreign.srt", "srt", null, true, false)]
+ [InlineData("/video/My Video.mkv", "/video/My Video.forced.srt", "srt", null, true, false)]
+ [InlineData("/video/My.Video.mkv", "/video/My.Video.default.srt", "srt", null, false, true)]
+ [InlineData("/video/My.Video.mkv", "/video/My.Video.forced.default.srt", "srt", null, true, true)]
+ [InlineData("/video/My.Video.mkv", "/video/My.Video.en.srt", "srt", "en", false, false)]
+ [InlineData("/video/My.Video.mkv", "/video/My.Video.default.en.srt", "srt", "en", false, true)]
+ [InlineData("/video/My.Video.mkv", "/video/My.Video.default.forced.en.srt", "srt", "en", true, true)]
+ [InlineData("/video/My.Video.mkv", "/video/My.Video.en.default.forced.srt", "srt", "en", true, true)]
+ public void AddExternalSubtitleStreams_GivenSingleFile_ReturnsExpectedSubtitle(string videoPath, string file, string codec, string? language, bool isForced, bool isDefault)
+ {
+ var streams = new List<MediaStream>();
+ var expected = CreateMediaStream(file, codec, language, 0, isForced, isDefault);
+
+ new SubtitleResolver(Mock.Of<ILocalizationManager>()).AddExternalSubtitleStreams(streams, videoPath, 0, new[] { file });
+
+ Assert.Single(streams);
+
+ var actual = streams[0];
+
+ Assert.Equal(expected.Index, actual.Index);
+ Assert.Equal(expected.Type, actual.Type);
+ Assert.Equal(expected.IsExternal, actual.IsExternal);
+ Assert.Equal(expected.Path, actual.Path);
+ Assert.Equal(expected.IsDefault, actual.IsDefault);
+ Assert.Equal(expected.IsForced, actual.IsForced);
+ Assert.Equal(expected.Language, actual.Language);
+ }
+
private static MediaStream CreateMediaStream(string path, string codec, string? language, int index, bool isForced = false, bool isDefault = false)
{
return new ()
diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs
index 976afe195..09aec82b0 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs
@@ -61,7 +61,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
{
Name = "The Big Bang Theory",
IsProgramSeries = true,
- OriginalAirDate = new DateTime(2018, 12, 6)
+ OriginalAirDate = new DateTime(2018, 12, 6, 0, 0, 0, DateTimeKind.Local)
});
data.Add(
@@ -70,7 +70,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
{
Name = "The Big Bang Theory",
IsProgramSeries = true,
- OriginalAirDate = new DateTime(2018, 12, 6),
+ OriginalAirDate = new DateTime(2018, 12, 6, 0, 0, 0, DateTimeKind.Local),
EpisodeTitle = "The VCR Illumination"
});
diff --git a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs
index 4ea05397d..4c8f64d1e 100644
--- a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs
@@ -1,6 +1,7 @@
using System;
using System.Net;
using System.Net.Http;
+using System.Net.Http.Json;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Text.Json;
@@ -26,14 +27,13 @@ namespace Jellyfin.Server.Integration.Tests
using var completeResponse = await client.PostAsync("/Startup/Complete", new ByteArrayContent(Array.Empty<byte>())).ConfigureAwait(false);
Assert.Equal(HttpStatusCode.NoContent, completeResponse.StatusCode);
- using var content = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(
+ using var content = JsonContent.Create(
new AuthenticateUserByName()
{
Username = user!.Name,
Pw = user.Password,
},
- jsonOptions));
- content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
+ options: jsonOptions);
content.Headers.Add("X-Emby-Authorization", DummyAuthHeader);
using var authResponse = await client.PostAsync("/Users/AuthenticateByName", content).ConfigureAwait(false);
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs
index 4421ced72..4c46933aa 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs
@@ -2,6 +2,7 @@ using System;
using System.Linq;
using System.Net;
using System.Net.Http;
+using System.Net.Http.Json;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Text;
@@ -62,9 +63,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Name = "ThisProfileDoesNotExist"
};
- using var content = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(deviceProfile, _jsonOptions));
- content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
- using var getResponse = await client.PostAsync("/Dlna/Profiles/" + NonExistentProfile, content).ConfigureAwait(false);
+ using var getResponse = await client.PostAsJsonAsync("/Dlna/Profiles/" + NonExistentProfile, deviceProfile, _jsonOptions).ConfigureAwait(false);
Assert.Equal(HttpStatusCode.NotFound, getResponse.StatusCode);
}
@@ -80,9 +79,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Name = "ThisProfileIsNew"
};
- using var content = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(deviceProfile, _jsonOptions));
- content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
- using var getResponse = await client.PostAsync("/Dlna/Profiles", content).ConfigureAwait(false);
+ using var getResponse = await client.PostAsJsonAsync("/Dlna/Profiles", deviceProfile, _jsonOptions).ConfigureAwait(false);
Assert.Equal(HttpStatusCode.NoContent, getResponse.StatusCode);
}
@@ -120,9 +117,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Id = _newDeviceProfileId
};
- using var content = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(updatedProfile, _jsonOptions));
- content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
- using var getResponse = await client.PostAsync("/Dlna/Profiles", content).ConfigureAwait(false);
+ using var getResponse = await client.PostAsJsonAsync("/Dlna/Profiles", updatedProfile, _jsonOptions).ConfigureAwait(false);
Assert.Equal(HttpStatusCode.NoContent, getResponse.StatusCode);
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs
index 19d8381ea..2da5237db 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs
@@ -1,6 +1,7 @@
using System;
using System.Net;
using System.Net.Http;
+using System.Net.Http.Json;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Text.Json;
@@ -71,9 +72,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Path = "/this/path/doesnt/exist"
};
- using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(data, _jsonOptions));
- postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
- var response = await client.PostAsync("Library/VirtualFolders/Paths", postContent).ConfigureAwait(false);
+ var response = await client.PostAsJsonAsync("Library/VirtualFolders/Paths", data, _jsonOptions).ConfigureAwait(false);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -90,9 +89,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
PathInfo = new MediaPathInfo("test")
};
- using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(data, _jsonOptions));
- postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
- var response = await client.PostAsync("Library/VirtualFolders/Paths/Update", postContent).ConfigureAwait(false);
+ var response = await client.PostAsJsonAsync("Library/VirtualFolders/Paths/Update", data, _jsonOptions).ConfigureAwait(false);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
index 9c0fc72f6..ed92ce25a 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
@@ -1,6 +1,7 @@
using System;
using System.Net;
using System.Net.Http;
+using System.Net.Http.Json;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Text.Json;
@@ -36,9 +37,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
PreferredMetadataLanguage = "nl"
};
- using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(config, _jsonOptions));
- postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
- using var postResponse = await client.PostAsync("/Startup/Configuration", postContent).ConfigureAwait(false);
+ using var postResponse = await client.PostAsJsonAsync("/Startup/Configuration", config, _jsonOptions).ConfigureAwait(false);
Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode);
using var getResponse = await client.GetAsync("/Startup/Configuration").ConfigureAwait(false);
@@ -80,9 +79,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Password = "NewPassword"
};
- using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions));
- postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
- var postResponse = await client.PostAsync("/Startup/User", postContent).ConfigureAwait(false);
+ var postResponse = await client.PostAsJsonAsync("/Startup/User", user, _jsonOptions).ConfigureAwait(false);
Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode);
var getResponse = await client.GetAsync("/Startup/User").ConfigureAwait(false);
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
index 8866ab53c..f11f276f8 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
@@ -3,6 +3,7 @@ using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
+using System.Net.Http.Json;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Text.Json;
@@ -31,18 +32,10 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
}
private Task<HttpResponseMessage> CreateUserByName(HttpClient httpClient, CreateUserByName request)
- {
- using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(request, _jsonOpions));
- postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
- return httpClient.PostAsync("Users/New", postContent);
- }
+ => httpClient.PostAsJsonAsync("Users/New", request, _jsonOpions);
private Task<HttpResponseMessage> UpdateUserPassword(HttpClient httpClient, Guid userId, UpdateUserPassword request)
- {
- using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(request, _jsonOpions));
- postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
- return httpClient.PostAsync("Users/" + userId.ToString("N", CultureInfo.InvariantCulture) + "/Password", postContent);
- }
+ => httpClient.PostAsJsonAsync("Users/" + userId.ToString("N", CultureInfo.InvariantCulture) + "/Password", request, _jsonOpions);
[Fact]
[Priority(-1)]