mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-11-15 18:08:53 -07:00
Updated to latest unstable.
This commit is contained in:
commit
889e988167
@ -22,6 +22,12 @@ jobs:
|
||||
BuildConfiguration: ubuntu.armhf
|
||||
Linux.amd64:
|
||||
BuildConfiguration: linux.amd64
|
||||
Linux.amd64-musl:
|
||||
BuildConfiguration: linux.amd64-musl
|
||||
Linux.arm64:
|
||||
BuildConfiguration: linux.arm64
|
||||
Linux.armhf:
|
||||
BuildConfiguration: linux.armhf
|
||||
Windows.amd64:
|
||||
BuildConfiguration: windows.amd64
|
||||
MacOS:
|
||||
|
@ -78,9 +78,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -126,6 +126,11 @@ namespace Emby.Dlna.Main
|
||||
|
||||
public static DlnaEntryPoint Current { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the dlna server is enabled.
|
||||
/// </summary>
|
||||
public static bool Enabled { get; private set; }
|
||||
|
||||
public IContentDirectory ContentDirectory { get; private set; }
|
||||
|
||||
public IConnectionManager ConnectionManager { get; private set; }
|
||||
@ -152,6 +157,7 @@ namespace Emby.Dlna.Main
|
||||
private void ReloadComponents()
|
||||
{
|
||||
var options = _config.GetDlnaConfiguration();
|
||||
Enabled = options.EnableServer;
|
||||
|
||||
StartSsdpHandler();
|
||||
|
||||
|
@ -52,7 +52,7 @@ namespace Emby.Dlna.Profiles
|
||||
Container = "ts,mpegts",
|
||||
Type = DlnaProfileType.Video,
|
||||
VideoCodec = "mpeg1video,mpeg2video,h264",
|
||||
AudioCodec = "ac3,mp2,mp3,aac"
|
||||
AudioCodec = "aac,ac3,mp2"
|
||||
},
|
||||
new DirectPlayProfile
|
||||
{
|
||||
@ -92,7 +92,7 @@ namespace Emby.Dlna.Profiles
|
||||
{
|
||||
Container = "ts",
|
||||
VideoCodec = "h264",
|
||||
AudioCodec = "ac3,aac,mp3",
|
||||
AudioCodec = "aac,ac3,mp2",
|
||||
Type = DlnaProfileType.Video
|
||||
},
|
||||
new TranscodingProfile
|
||||
|
@ -52,7 +52,7 @@ namespace Emby.Dlna.Profiles
|
||||
Container = "ts,mpegts",
|
||||
Type = DlnaProfileType.Video,
|
||||
VideoCodec = "mpeg1video,mpeg2video,h264",
|
||||
AudioCodec = "ac3,mp2,mp3,aac"
|
||||
AudioCodec = "aac,ac3,mp2"
|
||||
},
|
||||
new DirectPlayProfile
|
||||
{
|
||||
@ -94,7 +94,7 @@ namespace Emby.Dlna.Profiles
|
||||
{
|
||||
Container = "ts",
|
||||
VideoCodec = "h264",
|
||||
AudioCodec = "mp3",
|
||||
AudioCodec = "aac,ac3,mp2",
|
||||
Type = DlnaProfileType.Video
|
||||
},
|
||||
new TranscodingProfile
|
||||
|
@ -38,7 +38,7 @@
|
||||
<XmlRootAttributes />
|
||||
<DirectPlayProfiles>
|
||||
<DirectPlayProfile container="avi" audioCodec="mp2,mp3" videoCodec="mpeg4" type="Video" />
|
||||
<DirectPlayProfile container="ts,mpegts" audioCodec="ac3,mp2,mp3,aac" videoCodec="mpeg1video,mpeg2video,h264" type="Video" />
|
||||
<DirectPlayProfile container="ts,mpegts" audioCodec="aac,ac3,mp2" videoCodec="mpeg1video,mpeg2video,h264" type="Video" />
|
||||
<DirectPlayProfile container="mpeg" audioCodec="mp2" videoCodec="mpeg1video,mpeg2video" type="Video" />
|
||||
<DirectPlayProfile container="mp4" audioCodec="aac,ac3" videoCodec="h264,mpeg4" type="Video" />
|
||||
<DirectPlayProfile container="aac,mp3,wav" type="Audio" />
|
||||
@ -46,7 +46,7 @@
|
||||
</DirectPlayProfiles>
|
||||
<TranscodingProfiles>
|
||||
<TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" enableSubtitlesInManifest="false" minSegments="0" segmentLength="0" breakOnNonKeyFrames="false" />
|
||||
<TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3,aac,mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" enableSubtitlesInManifest="false" minSegments="0" segmentLength="0" breakOnNonKeyFrames="false" />
|
||||
<TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac,ac3,mp2" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" enableSubtitlesInManifest="false" minSegments="0" segmentLength="0" breakOnNonKeyFrames="false" />
|
||||
<TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" enableSubtitlesInManifest="false" minSegments="0" segmentLength="0" breakOnNonKeyFrames="false" />
|
||||
</TranscodingProfiles>
|
||||
<ContainerProfiles>
|
||||
|
@ -38,7 +38,7 @@
|
||||
<XmlRootAttributes />
|
||||
<DirectPlayProfiles>
|
||||
<DirectPlayProfile container="avi" audioCodec="mp2,mp3" videoCodec="mpeg4" type="Video" />
|
||||
<DirectPlayProfile container="ts,mpegts" audioCodec="ac3,mp2,mp3,aac" videoCodec="mpeg1video,mpeg2video,h264" type="Video" />
|
||||
<DirectPlayProfile container="ts,mpegts" audioCodec="aac,ac3,mp2" videoCodec="mpeg1video,mpeg2video,h264" type="Video" />
|
||||
<DirectPlayProfile container="mpeg" audioCodec="mp2" videoCodec="mpeg1video,mpeg2video" type="Video" />
|
||||
<DirectPlayProfile container="mp4,mkv,m4v" audioCodec="aac,ac3" videoCodec="h264,mpeg4" type="Video" />
|
||||
<DirectPlayProfile container="aac,mp3,wav" type="Audio" />
|
||||
@ -46,7 +46,7 @@
|
||||
</DirectPlayProfiles>
|
||||
<TranscodingProfiles>
|
||||
<TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Bytes" copyTimestamps="false" context="Streaming" enableSubtitlesInManifest="false" minSegments="0" segmentLength="0" breakOnNonKeyFrames="false" />
|
||||
<TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" enableSubtitlesInManifest="false" minSegments="0" segmentLength="0" breakOnNonKeyFrames="false" />
|
||||
<TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac,ac3,mp2" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" enableSubtitlesInManifest="false" minSegments="0" segmentLength="0" breakOnNonKeyFrames="false" />
|
||||
<TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" enableSubtitlesInManifest="false" minSegments="0" segmentLength="0" breakOnNonKeyFrames="false" />
|
||||
</TranscodingProfiles>
|
||||
<ContainerProfiles>
|
||||
|
@ -23,14 +23,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
||||
|
@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
|
||||
public User GetUser(object requestContext)
|
||||
{
|
||||
return GetUser((HttpContext)requestContext);
|
||||
return GetUser(((HttpRequest)requestContext).HttpContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -129,23 +129,23 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (!query.IncludeHidden)
|
||||
{
|
||||
list = list.Where(i => !user.GetPreference(PreferenceKind.MyMediaExcludes).Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))).ToList();
|
||||
list = list.Where(i => !user.GetPreferenceValues<Guid>(PreferenceKind.MyMediaExcludes).Contains(i.Id)).ToList();
|
||||
}
|
||||
|
||||
var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList();
|
||||
|
||||
var orders = user.GetPreference(PreferenceKind.OrderedViews).ToList();
|
||||
var orders = user.GetPreferenceValues<Guid>(PreferenceKind.OrderedViews);
|
||||
|
||||
return list
|
||||
.OrderBy(i =>
|
||||
{
|
||||
var index = orders.IndexOf(i.Id.ToString("D", CultureInfo.InvariantCulture));
|
||||
var index = Array.IndexOf(orders, i.Id);
|
||||
|
||||
if (index == -1
|
||||
&& i is UserView view
|
||||
&& view.DisplayParentId != Guid.Empty)
|
||||
{
|
||||
index = orders.IndexOf(view.DisplayParentId.ToString("D", CultureInfo.InvariantCulture));
|
||||
index = Array.IndexOf(orders, view.DisplayParentId);
|
||||
}
|
||||
|
||||
return index == -1 ? int.MaxValue : index;
|
||||
@ -280,8 +280,8 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
parents = _libraryManager.GetUserRootFolder().GetChildren(user, true)
|
||||
.Where(i => i is Folder)
|
||||
.Where(i => !user.GetPreference(PreferenceKind.LatestItemExcludes)
|
||||
.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture)))
|
||||
.Where(i => !user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes)
|
||||
.Contains(i.Id))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
@ -784,18 +784,17 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
var allStations = root.stations ?? new List<ScheduleDirect.Station>();
|
||||
|
||||
var map = root.map;
|
||||
int len = map.Count;
|
||||
var array = new List<ChannelInfo>(len);
|
||||
for (int i = 0; i < len; i++)
|
||||
var list = new List<ChannelInfo>(map.Count);
|
||||
foreach (var channel in map)
|
||||
{
|
||||
var channelNumber = GetChannelNumber(map[i]);
|
||||
var channelNumber = GetChannelNumber(channel);
|
||||
|
||||
var station = allStations.Find(item => string.Equals(item.stationID, map[i].stationID, StringComparison.OrdinalIgnoreCase));
|
||||
var station = allStations.Find(item => string.Equals(item.stationID, channel.stationID, StringComparison.OrdinalIgnoreCase));
|
||||
if (station == null)
|
||||
{
|
||||
station = new ScheduleDirect.Station
|
||||
{
|
||||
stationID = map[i].stationID
|
||||
stationID = channel.stationID
|
||||
};
|
||||
}
|
||||
|
||||
@ -812,10 +811,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
channelInfo.ImageUrl = station.logo.URL;
|
||||
}
|
||||
|
||||
array[i] = channelInfo;
|
||||
list.Add(channelInfo);
|
||||
}
|
||||
|
||||
return array;
|
||||
return list;
|
||||
}
|
||||
|
||||
private static string NormalizeName(string value)
|
||||
|
@ -115,5 +115,8 @@
|
||||
"TasksLibraryCategory": "Library",
|
||||
"TasksMaintenanceCategory": "Maintenance",
|
||||
"TaskCleanActivityLogDescription": "Deletes activity log entries older than the configured age.",
|
||||
"TaskCleanActivityLog": "Clean Activity Log"
|
||||
"TaskCleanActivityLog": "Clean Activity Log",
|
||||
"Undefined": "Undefined",
|
||||
"Forced": "Forced",
|
||||
"Default": "Default"
|
||||
}
|
||||
|
@ -112,5 +112,9 @@
|
||||
"CameraImageUploadedFrom": "Una nueva imagen de cámara ha sido subida desde {0}",
|
||||
"AuthenticationSucceededWithUserName": "{0} autenticado con éxito",
|
||||
"Application": "Aplicación",
|
||||
"AppDeviceValues": "App: {0}, Dispositivo: {1}"
|
||||
"AppDeviceValues": "App: {0}, Dispositivo: {1}",
|
||||
"TaskCleanActivityLogDescription": "Elimina las entradas del registro de actividad anteriores al periodo configurado.",
|
||||
"TaskCleanActivityLog": "Limpiar Registro de Actividades",
|
||||
"Undefined": "Sin definir",
|
||||
"Forced": "Forzado"
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
"Application": "アプリケーション",
|
||||
"Artists": "アーティスト",
|
||||
"AuthenticationSucceededWithUserName": "{0} 認証に成功しました",
|
||||
"Books": "ブック",
|
||||
"Books": "ブックス",
|
||||
"CameraImageUploadedFrom": "新しいカメライメージが {0}からアップロードされました",
|
||||
"Channels": "チャンネル",
|
||||
"ChapterNameValue": "チャプター {0}",
|
||||
@ -114,5 +114,8 @@
|
||||
"TaskRefreshChapterImages": "チャプター画像を抽出する",
|
||||
"TaskDownloadMissingSubtitles": "不足している字幕をダウンロードする",
|
||||
"TaskCleanActivityLogDescription": "設定された期間よりも古いアクティビティの履歴を削除します。",
|
||||
"TaskCleanActivityLog": "アクティビティの履歴を消去"
|
||||
"TaskCleanActivityLog": "アクティビティの履歴を消去",
|
||||
"Undefined": "未定義",
|
||||
"Forced": "強制",
|
||||
"Default": "デフォルト"
|
||||
}
|
||||
|
@ -91,5 +91,22 @@
|
||||
"UserStoppedPlayingItemWithValues": "{0} - {1} oınatýyn {2} toqtatty",
|
||||
"ValueHasBeenAddedToLibrary": "{0} (tasyǵyshhanaǵa ústelindi)",
|
||||
"ValueSpecialEpisodeName": "Arnaıy - {0}",
|
||||
"VersionNumber": "Nusqasy {0}"
|
||||
"VersionNumber": "Nusqasy {0}",
|
||||
"Default": "Ádepki",
|
||||
"TaskDownloadMissingSubtitles": "Joq sýbtıtrlerdi júktep alý",
|
||||
"TaskRefreshChannels": "Arnalardy jańartý",
|
||||
"TaskCleanTranscode": "Qaıta kodtaý katalogyn tazalaý",
|
||||
"TaskUpdatePlugins": "Plagınderdi jańartý",
|
||||
"TaskRefreshPeople": "Adamdardy jańartý",
|
||||
"TaskCleanLogs": "Jurnal katalogyn tazalaý",
|
||||
"TaskRefreshLibrary": "Tasyǵyshhanany skanerleý",
|
||||
"TaskRefreshChapterImages": "Sahna keskinderin shyǵaryp alý",
|
||||
"TaskCleanCache": "Kesh katalogyn tazalaý",
|
||||
"TaskCleanActivityLog": "Áreket jurnalyn tazalaý",
|
||||
"TasksChannelsCategory": "Internet-arnalar",
|
||||
"TasksApplicationCategory": "Qoldanba",
|
||||
"TasksLibraryCategory": "Tasyǵyshhana",
|
||||
"TasksMaintenanceCategory": "Qyzmet kórsetý",
|
||||
"Undefined": "Anyqtalmady",
|
||||
"Forced": "Májbúrli"
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"Albums": "Albums",
|
||||
"AppDeviceValues": "App: {0}, Apparaat: {1}",
|
||||
"Application": "Programma",
|
||||
"Application": "Applicatie",
|
||||
"Artists": "Artiesten",
|
||||
"AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd",
|
||||
"Books": "Boeken",
|
||||
|
@ -96,7 +96,7 @@
|
||||
"TaskRefreshChannels": "Обновление каналов",
|
||||
"TaskCleanTranscode": "Очистка каталога перекодировки",
|
||||
"TaskUpdatePlugins": "Обновление плагинов",
|
||||
"TaskRefreshPeople": "Обновление метаданных людей",
|
||||
"TaskRefreshPeople": "Подновить людей",
|
||||
"TaskCleanLogs": "Очистка каталога журналов",
|
||||
"TaskRefreshLibrary": "Сканирование медиатеки",
|
||||
"TaskRefreshChapterImages": "Извлечение изображений сцен",
|
||||
@ -109,7 +109,7 @@
|
||||
"TaskRefreshChannelsDescription": "Обновляются данные интернет-каналов.",
|
||||
"TaskCleanTranscodeDescription": "Удаляются файлы перекодировки старше одного дня.",
|
||||
"TaskUpdatePluginsDescription": "Загружаются и устанавливаются обновления для плагинов, у которых включено автоматическое обновление.",
|
||||
"TaskRefreshPeopleDescription": "Обновляются метаданные актеров и режиссёров в медиатеке.",
|
||||
"TaskRefreshPeopleDescription": "Обновляются метаданные для актёров и режиссёров в медиатеке.",
|
||||
"TaskCleanLogsDescription": "Удаляются файлы журнала, возраст которых превышает {0} дн(я/ей).",
|
||||
"TaskRefreshLibraryDescription": "Сканируется медиатека на новые файлы и обновляются метаданные.",
|
||||
"TaskRefreshChapterImagesDescription": "Создаются эскизы для видео, которые содержат сцены.",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"Inherit": "Наследи",
|
||||
"HomeVideos": "Кућни видео",
|
||||
"HeaderRecordingGroups": "Групе снимања",
|
||||
"HeaderNextUp": "Следеће горе",
|
||||
"HeaderNextUp": "Следи",
|
||||
"HeaderLiveTV": "ТВ уживо",
|
||||
"HeaderFavoriteSongs": "Омиљене песме",
|
||||
"HeaderFavoriteShows": "Омиљене серије",
|
||||
@ -79,17 +79,17 @@
|
||||
"Folders": "Фасцикле",
|
||||
"Favorites": "Омиљено",
|
||||
"FailedLoginAttemptWithUserName": "Неуспела пријава са {0}",
|
||||
"DeviceOnlineWithName": "{0} се повезао",
|
||||
"DeviceOnlineWithName": "{0} је повезан",
|
||||
"DeviceOfflineWithName": "{0} је прекинуо везу",
|
||||
"Collections": "Колекције",
|
||||
"ChapterNameValue": "Поглавље {0}",
|
||||
"Channels": "Канали",
|
||||
"CameraImageUploadedFrom": "Нова фотографија је послата са {0}",
|
||||
"CameraImageUploadedFrom": "Нова фотографија је учитана са {0}",
|
||||
"Books": "Књиге",
|
||||
"AuthenticationSucceededWithUserName": "{0} успешно проверено",
|
||||
"Artists": "Извођач",
|
||||
"Artists": "Извођачи",
|
||||
"Application": "Апликација",
|
||||
"AppDeviceValues": "Апл: {0}, уређај: {1}",
|
||||
"AppDeviceValues": "Апликација: {0}, Уређај: {1}",
|
||||
"Albums": "Албуми",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Претражује интернет за недостајуће титлове на основу конфигурације метаподатака.",
|
||||
"TaskDownloadMissingSubtitles": "Преузмите недостајуће титлове",
|
||||
@ -104,7 +104,7 @@
|
||||
"TaskCleanLogsDescription": "Брише логове старије од {0} дана.",
|
||||
"TaskCleanLogs": "Очистите директоријум логова",
|
||||
"TaskRefreshLibraryDescription": "Скенира вашу медијску библиотеку за нове датотеке и освежава метаподатке.",
|
||||
"TaskRefreshLibrary": "Скенирај Библиотеку Медија",
|
||||
"TaskRefreshLibrary": "Скенирај библиотеку медија",
|
||||
"TaskRefreshChapterImagesDescription": "Ствара сличице за видео записе који имају поглавља.",
|
||||
"TaskRefreshChapterImages": "Издвоји слике из поглавља",
|
||||
"TaskCleanCacheDescription": "Брише Кеш фајлове који више нису потребни систему.",
|
||||
@ -114,5 +114,8 @@
|
||||
"TasksLibraryCategory": "Библиотека",
|
||||
"TasksMaintenanceCategory": "Одржавање",
|
||||
"TaskCleanActivityLogDescription": "Брише историју активности старију од конфигурисаног броја година.",
|
||||
"TaskCleanActivityLog": "Очисти историју активности"
|
||||
"TaskCleanActivityLog": "Очисти историју активности",
|
||||
"Undefined": "Недефинисано",
|
||||
"Forced": "Форсирано",
|
||||
"Default": "Подразумевано"
|
||||
}
|
||||
|
@ -75,8 +75,7 @@ namespace Emby.Server.Implementations.TV
|
||||
{
|
||||
parents = _libraryManager.GetUserRootFolder().GetChildren(user, true)
|
||||
.Where(i => i is Folder)
|
||||
.Where(i => !user.GetPreference(PreferenceKind.LatestItemExcludes)
|
||||
.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture)))
|
||||
.Where(i => !user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes).Contains(i.Id))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
@ -85,6 +85,7 @@ namespace Jellyfin.Api.Controllers
|
||||
dto.CustomPrefs["skipBackLength"] = displayPreferences.SkipBackwardLength.ToString(CultureInfo.InvariantCulture);
|
||||
dto.CustomPrefs["enableNextVideoInfoOverlay"] = displayPreferences.EnableNextVideoInfoOverlay.ToString(CultureInfo.InvariantCulture);
|
||||
dto.CustomPrefs["tvhome"] = displayPreferences.TvHome;
|
||||
dto.CustomPrefs["dashboardTheme"] = displayPreferences.DashboardTheme;
|
||||
|
||||
// Load all custom display preferences
|
||||
var customDisplayPreferences = _displayPreferencesManager.ListCustomItemDisplayPreferences(displayPreferences.UserId, itemId, displayPreferences.Client);
|
||||
|
@ -41,18 +41,25 @@ namespace Jellyfin.Api.Controllers
|
||||
/// </summary>
|
||||
/// <param name="serverId">Server UUID.</param>
|
||||
/// <response code="200">Description xml returned.</response>
|
||||
/// <response code="503">DLNA is disabled.</response>
|
||||
/// <returns>An <see cref="OkResult"/> containing the description xml.</returns>
|
||||
[HttpGet("{serverId}/description")]
|
||||
[HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
|
||||
[Produces(MediaTypeNames.Text.Xml)]
|
||||
[ProducesFile(MediaTypeNames.Text.Xml)]
|
||||
public ActionResult GetDescriptionXml([FromRoute, Required] string serverId)
|
||||
{
|
||||
var url = GetAbsoluteUri();
|
||||
var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
|
||||
var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, serverId, serverAddress);
|
||||
return Ok(xml);
|
||||
if (DlnaEntryPoint.Enabled)
|
||||
{
|
||||
var url = GetAbsoluteUri();
|
||||
var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
|
||||
var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, serverId, serverAddress);
|
||||
return Ok(xml);
|
||||
}
|
||||
|
||||
return StatusCode(StatusCodes.Status503ServiceUnavailable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -60,17 +67,24 @@ namespace Jellyfin.Api.Controllers
|
||||
/// </summary>
|
||||
/// <param name="serverId">Server UUID.</param>
|
||||
/// <response code="200">Dlna content directory returned.</response>
|
||||
/// <response code="503">DLNA is disabled.</response>
|
||||
/// <returns>An <see cref="OkResult"/> containing the dlna content directory xml.</returns>
|
||||
[HttpGet("{serverId}/ContentDirectory")]
|
||||
[HttpGet("{serverId}/ContentDirectory/ContentDirectory", Name = "GetContentDirectory_2")]
|
||||
[HttpGet("{serverId}/ContentDirectory/ContentDirectory.xml", Name = "GetContentDirectory_3")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
|
||||
[Produces(MediaTypeNames.Text.Xml)]
|
||||
[ProducesFile(MediaTypeNames.Text.Xml)]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
|
||||
public ActionResult GetContentDirectory([FromRoute, Required] string serverId)
|
||||
{
|
||||
return Ok(_contentDirectory.GetServiceXml());
|
||||
if (DlnaEntryPoint.Enabled)
|
||||
{
|
||||
return Ok(_contentDirectory.GetServiceXml());
|
||||
}
|
||||
|
||||
return StatusCode(StatusCodes.Status503ServiceUnavailable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -78,17 +92,24 @@ namespace Jellyfin.Api.Controllers
|
||||
/// </summary>
|
||||
/// <param name="serverId">Server UUID.</param>
|
||||
/// <response code="200">Dlna media receiver registrar xml returned.</response>
|
||||
/// <response code="503">DLNA is disabled.</response>
|
||||
/// <returns>Dlna media receiver registrar xml.</returns>
|
||||
[HttpGet("{serverId}/MediaReceiverRegistrar")]
|
||||
[HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar", Name = "GetMediaReceiverRegistrar_2")]
|
||||
[HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_3")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
|
||||
[Produces(MediaTypeNames.Text.Xml)]
|
||||
[ProducesFile(MediaTypeNames.Text.Xml)]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
|
||||
public ActionResult GetMediaReceiverRegistrar([FromRoute, Required] string serverId)
|
||||
{
|
||||
return Ok(_mediaReceiverRegistrar.GetServiceXml());
|
||||
if (DlnaEntryPoint.Enabled)
|
||||
{
|
||||
return Ok(_mediaReceiverRegistrar.GetServiceXml());
|
||||
}
|
||||
|
||||
return StatusCode(StatusCodes.Status503ServiceUnavailable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -96,17 +117,24 @@ namespace Jellyfin.Api.Controllers
|
||||
/// </summary>
|
||||
/// <param name="serverId">Server UUID.</param>
|
||||
/// <response code="200">Dlna media receiver registrar xml returned.</response>
|
||||
/// <response code="503">DLNA is disabled.</response>
|
||||
/// <returns>Dlna media receiver registrar xml.</returns>
|
||||
[HttpGet("{serverId}/ConnectionManager")]
|
||||
[HttpGet("{serverId}/ConnectionManager/ConnectionManager", Name = "GetConnectionManager_2")]
|
||||
[HttpGet("{serverId}/ConnectionManager/ConnectionManager.xml", Name = "GetConnectionManager_3")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
|
||||
[Produces(MediaTypeNames.Text.Xml)]
|
||||
[ProducesFile(MediaTypeNames.Text.Xml)]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
|
||||
public ActionResult GetConnectionManager([FromRoute, Required] string serverId)
|
||||
{
|
||||
return Ok(_connectionManager.GetServiceXml());
|
||||
if (DlnaEntryPoint.Enabled)
|
||||
{
|
||||
return Ok(_connectionManager.GetServiceXml());
|
||||
}
|
||||
|
||||
return StatusCode(StatusCodes.Status503ServiceUnavailable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -114,14 +142,21 @@ namespace Jellyfin.Api.Controllers
|
||||
/// </summary>
|
||||
/// <param name="serverId">Server UUID.</param>
|
||||
/// <response code="200">Request processed.</response>
|
||||
/// <response code="503">DLNA is disabled.</response>
|
||||
/// <returns>Control response.</returns>
|
||||
[HttpPost("{serverId}/ContentDirectory/Control")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
|
||||
[Produces(MediaTypeNames.Text.Xml)]
|
||||
[ProducesFile(MediaTypeNames.Text.Xml)]
|
||||
public async Task<ActionResult<ControlResponse>> ProcessContentDirectoryControlRequest([FromRoute, Required] string serverId)
|
||||
{
|
||||
return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false);
|
||||
if (DlnaEntryPoint.Enabled)
|
||||
{
|
||||
return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return StatusCode(StatusCodes.Status503ServiceUnavailable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -129,14 +164,21 @@ namespace Jellyfin.Api.Controllers
|
||||
/// </summary>
|
||||
/// <param name="serverId">Server UUID.</param>
|
||||
/// <response code="200">Request processed.</response>
|
||||
/// <response code="503">DLNA is disabled.</response>
|
||||
/// <returns>Control response.</returns>
|
||||
[HttpPost("{serverId}/ConnectionManager/Control")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
|
||||
[Produces(MediaTypeNames.Text.Xml)]
|
||||
[ProducesFile(MediaTypeNames.Text.Xml)]
|
||||
public async Task<ActionResult<ControlResponse>> ProcessConnectionManagerControlRequest([FromRoute, Required] string serverId)
|
||||
{
|
||||
return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false);
|
||||
if (DlnaEntryPoint.Enabled)
|
||||
{
|
||||
return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return StatusCode(StatusCodes.Status503ServiceUnavailable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -144,14 +186,21 @@ namespace Jellyfin.Api.Controllers
|
||||
/// </summary>
|
||||
/// <param name="serverId">Server UUID.</param>
|
||||
/// <response code="200">Request processed.</response>
|
||||
/// <response code="503">DLNA is disabled.</response>
|
||||
/// <returns>Control response.</returns>
|
||||
[HttpPost("{serverId}/MediaReceiverRegistrar/Control")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
|
||||
[Produces(MediaTypeNames.Text.Xml)]
|
||||
[ProducesFile(MediaTypeNames.Text.Xml)]
|
||||
public async Task<ActionResult<ControlResponse>> ProcessMediaReceiverRegistrarControlRequest([FromRoute, Required] string serverId)
|
||||
{
|
||||
return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false);
|
||||
if (DlnaEntryPoint.Enabled)
|
||||
{
|
||||
return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return StatusCode(StatusCodes.Status503ServiceUnavailable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -159,17 +208,24 @@ namespace Jellyfin.Api.Controllers
|
||||
/// </summary>
|
||||
/// <param name="serverId">Server UUID.</param>
|
||||
/// <response code="200">Request processed.</response>
|
||||
/// <response code="503">DLNA is disabled.</response>
|
||||
/// <returns>Event subscription response.</returns>
|
||||
[HttpSubscribe("{serverId}/MediaReceiverRegistrar/Events")]
|
||||
[HttpUnsubscribe("{serverId}/MediaReceiverRegistrar/Events")]
|
||||
[ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
|
||||
[Produces(MediaTypeNames.Text.Xml)]
|
||||
[ProducesFile(MediaTypeNames.Text.Xml)]
|
||||
public ActionResult<EventSubscriptionResponse> ProcessMediaReceiverRegistrarEventRequest(string serverId)
|
||||
{
|
||||
return ProcessEventRequest(_mediaReceiverRegistrar);
|
||||
if (DlnaEntryPoint.Enabled)
|
||||
{
|
||||
return ProcessEventRequest(_mediaReceiverRegistrar);
|
||||
}
|
||||
|
||||
return StatusCode(StatusCodes.Status503ServiceUnavailable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -177,17 +233,24 @@ namespace Jellyfin.Api.Controllers
|
||||
/// </summary>
|
||||
/// <param name="serverId">Server UUID.</param>
|
||||
/// <response code="200">Request processed.</response>
|
||||
/// <response code="503">DLNA is disabled.</response>
|
||||
/// <returns>Event subscription response.</returns>
|
||||
[HttpSubscribe("{serverId}/ContentDirectory/Events")]
|
||||
[HttpUnsubscribe("{serverId}/ContentDirectory/Events")]
|
||||
[ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
|
||||
[Produces(MediaTypeNames.Text.Xml)]
|
||||
[ProducesFile(MediaTypeNames.Text.Xml)]
|
||||
public ActionResult<EventSubscriptionResponse> ProcessContentDirectoryEventRequest(string serverId)
|
||||
{
|
||||
return ProcessEventRequest(_contentDirectory);
|
||||
if (DlnaEntryPoint.Enabled)
|
||||
{
|
||||
return ProcessEventRequest(_contentDirectory);
|
||||
}
|
||||
|
||||
return StatusCode(StatusCodes.Status503ServiceUnavailable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -195,17 +258,24 @@ namespace Jellyfin.Api.Controllers
|
||||
/// </summary>
|
||||
/// <param name="serverId">Server UUID.</param>
|
||||
/// <response code="200">Request processed.</response>
|
||||
/// <response code="503">DLNA is disabled.</response>
|
||||
/// <returns>Event subscription response.</returns>
|
||||
[HttpSubscribe("{serverId}/ConnectionManager/Events")]
|
||||
[HttpUnsubscribe("{serverId}/ConnectionManager/Events")]
|
||||
[ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
|
||||
[Produces(MediaTypeNames.Text.Xml)]
|
||||
[ProducesFile(MediaTypeNames.Text.Xml)]
|
||||
public ActionResult<EventSubscriptionResponse> ProcessConnectionManagerEventRequest(string serverId)
|
||||
{
|
||||
return ProcessEventRequest(_connectionManager);
|
||||
if (DlnaEntryPoint.Enabled)
|
||||
{
|
||||
return ProcessEventRequest(_connectionManager);
|
||||
}
|
||||
|
||||
return StatusCode(StatusCodes.Status503ServiceUnavailable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -213,14 +283,24 @@ namespace Jellyfin.Api.Controllers
|
||||
/// </summary>
|
||||
/// <param name="serverId">Server UUID.</param>
|
||||
/// <param name="fileName">The icon filename.</param>
|
||||
/// <response code="200">Request processed.</response>
|
||||
/// <response code="404">Not Found.</response>
|
||||
/// <response code="503">DLNA is disabled.</response>
|
||||
/// <returns>Icon stream.</returns>
|
||||
[HttpGet("{serverId}/icons/{fileName}")]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
|
||||
[ProducesImageFile]
|
||||
public ActionResult GetIconId([FromRoute, Required] string serverId, [FromRoute, Required] string fileName)
|
||||
{
|
||||
return GetIconInternal(fileName);
|
||||
if (DlnaEntryPoint.Enabled)
|
||||
{
|
||||
return GetIconInternal(fileName);
|
||||
}
|
||||
|
||||
return StatusCode(StatusCodes.Status503ServiceUnavailable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -228,11 +308,22 @@ namespace Jellyfin.Api.Controllers
|
||||
/// </summary>
|
||||
/// <param name="fileName">The icon filename.</param>
|
||||
/// <returns>Icon stream.</returns>
|
||||
/// <response code="200">Request processed.</response>
|
||||
/// <response code="404">Not Found.</response>
|
||||
/// <response code="503">DLNA is disabled.</response>
|
||||
[HttpGet("icons/{fileName}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
|
||||
[ProducesImageFile]
|
||||
public ActionResult GetIcon([FromRoute, Required] string fileName)
|
||||
{
|
||||
return GetIconInternal(fileName);
|
||||
if (DlnaEntryPoint.Enabled)
|
||||
{
|
||||
return GetIconInternal(fileName);
|
||||
}
|
||||
|
||||
return StatusCode(StatusCodes.Status503ServiceUnavailable);
|
||||
}
|
||||
|
||||
private ActionResult GetIconInternal(string fileName)
|
||||
|
@ -325,9 +325,11 @@ namespace Jellyfin.Api.Controllers
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
await using var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false);
|
||||
|
||||
// Handle image/png; charset=utf-8
|
||||
var mimeType = Request.ContentType.Split(';').FirstOrDefault();
|
||||
await _providerManager.SaveImage(item, Request.Body, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
|
||||
await _providerManager.SaveImage(item, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
|
||||
await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
return NoContent();
|
||||
@ -358,9 +360,11 @@ namespace Jellyfin.Api.Controllers
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
await using var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false);
|
||||
|
||||
// Handle image/png; charset=utf-8
|
||||
var mimeType = Request.ContentType.Split(';').FirstOrDefault();
|
||||
await _providerManager.SaveImage(item, Request.Body, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
|
||||
await _providerManager.SaveImage(item, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
|
||||
await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
return NoContent();
|
||||
|
@ -254,18 +254,18 @@ namespace Jellyfin.Api.Controllers
|
||||
includeItemTypes = new[] { "Playlist" };
|
||||
}
|
||||
|
||||
bool isInEnabledFolder = user!.GetPreference(PreferenceKind.EnabledFolders).Any(i => new Guid(i) == item.Id)
|
||||
var enabledChannels = user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels);
|
||||
|
||||
bool isInEnabledFolder = Array.IndexOf(user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders), item.Id) != -1
|
||||
// Assume all folders inside an EnabledChannel are enabled
|
||||
|| user.GetPreference(PreferenceKind.EnabledChannels).Any(i => new Guid(i) == item.Id)
|
||||
|| Array.IndexOf(enabledChannels, item.Id) != -1
|
||||
// Assume all items inside an EnabledChannel are enabled
|
||||
|| user.GetPreference(PreferenceKind.EnabledChannels).Any(i => new Guid(i) == item.ChannelId);
|
||||
|| Array.IndexOf(enabledChannels, item.ChannelId) != -1;
|
||||
|
||||
var collectionFolders = _libraryManager.GetCollectionFolders(item);
|
||||
foreach (var collectionFolder in collectionFolders)
|
||||
{
|
||||
if (user.GetPreference(PreferenceKind.EnabledFolders).Contains(
|
||||
collectionFolder.Id.ToString("N", CultureInfo.InvariantCulture),
|
||||
StringComparer.OrdinalIgnoreCase))
|
||||
if (user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders).Contains(collectionFolder.Id))
|
||||
{
|
||||
isInEnabledFolder = true;
|
||||
}
|
||||
@ -786,12 +786,12 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
var ancestorIds = Array.Empty<Guid>();
|
||||
|
||||
var excludeFolderIds = user.GetPreference(PreferenceKind.LatestItemExcludes);
|
||||
var excludeFolderIds = user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes);
|
||||
if (parentIdGuid.Equals(Guid.Empty) && excludeFolderIds.Length > 0)
|
||||
{
|
||||
ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true)
|
||||
.Where(i => i is Folder)
|
||||
.Where(i => !excludeFolderIds.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture)))
|
||||
.Where(i => !excludeFolderIds.Contains(i.Id))
|
||||
.Select(i => i.Id)
|
||||
.ToArray();
|
||||
}
|
||||
|
@ -667,7 +667,7 @@ namespace Jellyfin.Api.Controllers
|
||||
}
|
||||
|
||||
// TODO determine non-ASCII validity.
|
||||
return PhysicalFile(path, MimeTypes.GetMimeType(path));
|
||||
return PhysicalFile(path, MimeTypes.GetMimeType(path), filename);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -742,8 +742,6 @@ namespace Jellyfin.Api.Controllers
|
||||
{
|
||||
Limit = limit,
|
||||
IncludeItemTypes = includeItemTypes.ToArray(),
|
||||
IsMovie = isMovie,
|
||||
IsSeries = isSeries,
|
||||
SimilarTo = item,
|
||||
DtoOptions = dtoOptions,
|
||||
EnableTotalRecordCount = !isMovie ?? true,
|
||||
|
@ -1119,20 +1119,15 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <summary>
|
||||
/// Set channel mappings.
|
||||
/// </summary>
|
||||
/// <param name="providerId">Provider id.</param>
|
||||
/// <param name="tunerChannelId">Tuner channel id.</param>
|
||||
/// <param name="providerChannelId">Provider channel id.</param>
|
||||
/// <param name="setChannelMappingDto">The set channel mapping dto.</param>
|
||||
/// <response code="200">Created channel mapping returned.</response>
|
||||
/// <returns>An <see cref="OkResult"/> containing the created channel mapping.</returns>
|
||||
[HttpPost("ChannelMappings")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TunerChannelMapping>> SetChannelMapping(
|
||||
[FromQuery] string? providerId,
|
||||
[FromQuery] string? tunerChannelId,
|
||||
[FromQuery] string? providerChannelId)
|
||||
public async Task<ActionResult<TunerChannelMapping>> SetChannelMapping([FromBody, Required] SetChannelMappingDto setChannelMappingDto)
|
||||
{
|
||||
return await _liveTvManager.SetChannelMapping(providerId, tunerChannelId, providerChannelId).ConfigureAwait(false);
|
||||
return await _liveTvManager.SetChannelMapping(setChannelMappingDto.ProviderId, setChannelMappingDto.TunerChannelId, setChannelMappingDto.ProviderChannelId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -259,24 +259,24 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromQuery] int? subtitleStreamIndex,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] Guid? itemId,
|
||||
[FromBody] OpenLiveStreamDto openLiveStreamDto,
|
||||
[FromQuery] bool enableDirectPlay = true,
|
||||
[FromQuery] bool enableDirectStream = true)
|
||||
[FromBody] OpenLiveStreamDto? openLiveStreamDto,
|
||||
[FromQuery] bool? enableDirectPlay,
|
||||
[FromQuery] bool? enableDirectStream)
|
||||
{
|
||||
var request = new LiveStreamRequest
|
||||
{
|
||||
OpenToken = openToken,
|
||||
UserId = userId ?? Guid.Empty,
|
||||
PlaySessionId = playSessionId,
|
||||
MaxStreamingBitrate = maxStreamingBitrate,
|
||||
StartTimeTicks = startTimeTicks,
|
||||
AudioStreamIndex = audioStreamIndex,
|
||||
SubtitleStreamIndex = subtitleStreamIndex,
|
||||
MaxAudioChannels = maxAudioChannels,
|
||||
ItemId = itemId ?? Guid.Empty,
|
||||
OpenToken = openToken ?? openLiveStreamDto?.OpenToken,
|
||||
UserId = userId ?? openLiveStreamDto?.UserId ?? Guid.Empty,
|
||||
PlaySessionId = playSessionId ?? openLiveStreamDto?.PlaySessionId,
|
||||
MaxStreamingBitrate = maxStreamingBitrate ?? openLiveStreamDto?.MaxStreamingBitrate,
|
||||
StartTimeTicks = startTimeTicks ?? openLiveStreamDto?.StartTimeTicks,
|
||||
AudioStreamIndex = audioStreamIndex ?? openLiveStreamDto?.AudioStreamIndex,
|
||||
SubtitleStreamIndex = subtitleStreamIndex ?? openLiveStreamDto?.SubtitleStreamIndex,
|
||||
MaxAudioChannels = maxAudioChannels ?? openLiveStreamDto?.MaxAudioChannels,
|
||||
ItemId = itemId ?? openLiveStreamDto?.ItemId ?? Guid.Empty,
|
||||
DeviceProfile = openLiveStreamDto?.DeviceProfile,
|
||||
EnableDirectPlay = enableDirectPlay,
|
||||
EnableDirectStream = enableDirectStream,
|
||||
EnableDirectPlay = enableDirectPlay ?? openLiveStreamDto?.EnableDirectPlay ?? true,
|
||||
EnableDirectStream = enableDirectStream ?? openLiveStreamDto?.EnableDirectStream ?? true,
|
||||
DirectPlayProtocols = openLiveStreamDto?.DirectPlayProtocols ?? new[] { MediaProtocol.Http }
|
||||
};
|
||||
return await _mediaInfoHelper.OpenMediaSource(Request, request).ConfigureAwait(false);
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Mime;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -66,7 +67,7 @@ namespace Jellyfin.Api.Controllers
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<SystemInfo> GetSystemInfo()
|
||||
{
|
||||
return _appHost.GetSystemInfo(Request.HttpContext.Connection.RemoteIpAddress);
|
||||
return _appHost.GetSystemInfo(Request.HttpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -78,7 +79,7 @@ namespace Jellyfin.Api.Controllers
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<PublicSystemInfo> GetPublicSystemInfo()
|
||||
{
|
||||
return _appHost.GetPublicSystemInfo(Request.HttpContext.Connection.RemoteIpAddress);
|
||||
return _appHost.GetPublicSystemInfo(Request.HttpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -15,9 +15,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="5.6.3" />
|
||||
|
28
Jellyfin.Api/Models/LiveTvDtos/SetChannelMappingDto.cs
Normal file
28
Jellyfin.Api/Models/LiveTvDtos/SetChannelMappingDto.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Jellyfin.Api.Models.LiveTvDtos
|
||||
{
|
||||
/// <summary>
|
||||
/// Set channel mapping dto.
|
||||
/// </summary>
|
||||
public class SetChannelMappingDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the provider id.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string ProviderId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tuner channel id.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string TunerChannelId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the provider channel id.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string ProviderChannelId { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
@ -10,6 +10,61 @@ namespace Jellyfin.Api.Models.MediaInfoDtos
|
||||
/// </summary>
|
||||
public class OpenLiveStreamDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the open token.
|
||||
/// </summary>
|
||||
public string? OpenToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user id.
|
||||
/// </summary>
|
||||
public Guid? UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the play session id.
|
||||
/// </summary>
|
||||
public string? PlaySessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the max streaming bitrate.
|
||||
/// </summary>
|
||||
public int? MaxStreamingBitrate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the start time in ticks.
|
||||
/// </summary>
|
||||
public long? StartTimeTicks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audio stream index.
|
||||
/// </summary>
|
||||
public int? AudioStreamIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the subtitle stream index.
|
||||
/// </summary>
|
||||
public int? SubtitleStreamIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the max audio channels.
|
||||
/// </summary>
|
||||
public int? MaxAudioChannels { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the item id.
|
||||
/// </summary>
|
||||
public Guid? ItemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to enable direct play.
|
||||
/// </summary>
|
||||
public bool? EnableDirectPlay { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to enale direct stream.
|
||||
/// </summary>
|
||||
public bool? EnableDirectStream { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the device profile.
|
||||
/// </summary>
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Jellyfin.Data.Enums;
|
||||
@ -413,6 +413,44 @@ namespace Jellyfin.Data.Entities
|
||||
return Equals(val, string.Empty) ? Array.Empty<string>() : val.Split(Delimiter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user's preferences for the given preference kind.
|
||||
/// </summary>
|
||||
/// <param name="preference">The preference kind.</param>
|
||||
/// <typeparam name="T">Type of preference.</typeparam>
|
||||
/// <returns>A {T} array containing the user's preference.</returns>
|
||||
public T[] GetPreferenceValues<T>(PreferenceKind preference)
|
||||
{
|
||||
var val = Preferences.First(p => p.Kind == preference).Value;
|
||||
if (string.IsNullOrEmpty(val))
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
// Convert array of {string} to array of {T}
|
||||
var converter = TypeDescriptor.GetConverter(typeof(T));
|
||||
var stringValues = val.Split(Delimiter);
|
||||
var convertedCount = 0;
|
||||
var parsedValues = new T[stringValues.Length];
|
||||
for (var i = 0; i < stringValues.Length; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var parsedValue = converter.ConvertFromString(stringValues[i].Trim());
|
||||
if (parsedValue != null)
|
||||
{
|
||||
parsedValues[convertedCount++] = (T)parsedValue;
|
||||
}
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
// Unable to convert value
|
||||
}
|
||||
}
|
||||
|
||||
return parsedValues[..convertedCount];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified preference to the given value.
|
||||
/// </summary>
|
||||
@ -421,7 +459,19 @@ namespace Jellyfin.Data.Entities
|
||||
public void SetPreference(PreferenceKind preference, string[] values)
|
||||
{
|
||||
Preferences.First(p => p.Kind == preference).Value
|
||||
= string.Join(Delimiter.ToString(CultureInfo.InvariantCulture), values);
|
||||
= string.Join(Delimiter, values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified preference to the given value.
|
||||
/// </summary>
|
||||
/// <param name="preference">The preference kind.</param>
|
||||
/// <param name="values">The values.</param>
|
||||
/// <typeparam name="T">The type of value.</typeparam>
|
||||
public void SetPreference<T>(PreferenceKind preference, T[] values)
|
||||
{
|
||||
Preferences.First(p => p.Kind == preference).Value
|
||||
= string.Join(Delimiter, values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -441,7 +491,7 @@ namespace Jellyfin.Data.Entities
|
||||
/// <returns><c>True</c> if the folder is in the user's grouped folders.</returns>
|
||||
public bool IsFolderGrouped(Guid id)
|
||||
{
|
||||
return GetPreference(PreferenceKind.GroupedFolders).Any(i => new Guid(i) == id);
|
||||
return Array.IndexOf(GetPreferenceValues<Guid>(PreferenceKind.GroupedFolders), id) != -1;
|
||||
}
|
||||
|
||||
private static bool IsParentalScheduleAllowed(AccessSchedule schedule, DateTime date)
|
||||
|
@ -375,14 +375,14 @@ namespace Jellyfin.Server.Implementations.Users
|
||||
EnablePublicSharing = user.HasPermission(PermissionKind.EnablePublicSharing),
|
||||
AccessSchedules = user.AccessSchedules.ToArray(),
|
||||
BlockedTags = user.GetPreference(PreferenceKind.BlockedTags),
|
||||
EnabledChannels = user.GetPreference(PreferenceKind.EnabledChannels)?.Select(Guid.Parse).ToArray(),
|
||||
EnabledChannels = user.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels),
|
||||
EnabledDevices = user.GetPreference(PreferenceKind.EnabledDevices),
|
||||
EnabledFolders = user.GetPreference(PreferenceKind.EnabledFolders)?.Select(Guid.Parse).ToArray(),
|
||||
EnabledFolders = user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders),
|
||||
EnableContentDeletionFromFolders = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders),
|
||||
SyncPlayAccess = user.SyncPlayAccess,
|
||||
BlockedChannels = user.GetPreference(PreferenceKind.BlockedChannels)?.Select(Guid.Parse).ToArray(),
|
||||
BlockedMediaFolders = user.GetPreference(PreferenceKind.BlockedMediaFolders)?.Select(Guid.Parse).ToArray(),
|
||||
BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems).Select(Enum.Parse<UnratedItem>).ToArray()
|
||||
BlockedChannels = user.GetPreferenceValues<Guid>(PreferenceKind.BlockedChannels),
|
||||
BlockedMediaFolders = user.GetPreferenceValues<Guid>(PreferenceKind.BlockedMediaFolders),
|
||||
BlockUnratedItems = user.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems)
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -703,13 +703,11 @@ namespace Jellyfin.Server.Implementations.Users
|
||||
}
|
||||
|
||||
// TODO: fix this at some point
|
||||
user.SetPreference(
|
||||
PreferenceKind.BlockUnratedItems,
|
||||
policy.BlockUnratedItems?.Select(i => i.ToString()).ToArray() ?? Array.Empty<string>());
|
||||
user.SetPreference(PreferenceKind.BlockUnratedItems, policy.BlockUnratedItems ?? Array.Empty<UnratedItem>());
|
||||
user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags);
|
||||
user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels?.Select(i => i.ToString("N", CultureInfo.InvariantCulture)).ToArray());
|
||||
user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels);
|
||||
user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices);
|
||||
user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders?.Select(i => i.ToString("N", CultureInfo.InvariantCulture)).ToArray());
|
||||
user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders);
|
||||
user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
|
||||
|
||||
dbContext.Update(user);
|
||||
|
@ -8,7 +8,6 @@ using System.Text.Json.Serialization;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Entities;
|
||||
@ -81,7 +80,8 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
{ "unstable", ChromecastVersion.Unstable }
|
||||
};
|
||||
|
||||
var customDisplayPrefs = new HashSet<string>();
|
||||
var displayPrefs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var customDisplayPrefs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var dbFilePath = Path.Combine(_paths.DataPath, DbFilename);
|
||||
using (var connection = SQLite3.Open(dbFilePath, ConnectionFlags.ReadOnly, null))
|
||||
{
|
||||
@ -98,6 +98,15 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
|
||||
var itemId = new Guid(result[1].ToBlob());
|
||||
var dtoUserId = new Guid(result[1].ToBlob());
|
||||
var client = result[2].ToString();
|
||||
var displayPreferencesKey = $"{dtoUserId}|{itemId}|{client}";
|
||||
if (displayPrefs.Contains(displayPreferencesKey))
|
||||
{
|
||||
// Duplicate display preference.
|
||||
continue;
|
||||
}
|
||||
|
||||
displayPrefs.Add(displayPreferencesKey);
|
||||
var existingUser = _userManager.GetUserById(dtoUserId);
|
||||
if (existingUser == null)
|
||||
{
|
||||
@ -110,7 +119,7 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
: ChromecastVersion.Stable;
|
||||
dto.CustomPrefs.Remove("chromecastVersion");
|
||||
|
||||
var displayPreferences = new DisplayPreferences(dtoUserId, itemId, result[2].ToString())
|
||||
var displayPreferences = new DisplayPreferences(dtoUserId, itemId, client)
|
||||
{
|
||||
IndexBy = Enum.TryParse<IndexingKind>(dto.IndexBy, true, out var indexBy) ? indexBy : (IndexingKind?)null,
|
||||
ShowBackdrop = dto.ShowBackdrop,
|
||||
|
@ -168,9 +168,9 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
}
|
||||
|
||||
user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags);
|
||||
user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels?.Select(i => i.ToString("N", CultureInfo.InvariantCulture)).ToArray());
|
||||
user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels);
|
||||
user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices);
|
||||
user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders?.Select(i => i.ToString("N", CultureInfo.InvariantCulture)).ToArray());
|
||||
user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders);
|
||||
user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
|
||||
user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews);
|
||||
user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders);
|
||||
|
34
MediaBrowser.Common/Json/Converters/JsonDateTimeConverter.cs
Normal file
34
MediaBrowser.Common/Json/Converters/JsonDateTimeConverter.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace MediaBrowser.Common.Json.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy DateTime converter.
|
||||
/// Milliseconds aren't output if zero by default.
|
||||
/// </summary>
|
||||
public class JsonDateTimeConverter : JsonConverter<DateTime>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return reader.GetDateTime();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
|
||||
{
|
||||
if (value.Millisecond == 0)
|
||||
{
|
||||
// Remaining ticks value will be 0, manually format.
|
||||
writer.WriteStringValue(value.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffZ", CultureInfo.InvariantCulture));
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteStringValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -19,56 +19,71 @@ namespace MediaBrowser.Common.Json
|
||||
/// </summary>
|
||||
public const string CamelCaseMediaType = "application/json; profile=\"CamelCase\"";
|
||||
|
||||
/// <summary>
|
||||
/// When changing these options, update
|
||||
/// Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
|
||||
/// -> AddJellyfinApi
|
||||
/// -> AddJsonOptions.
|
||||
/// </summary>
|
||||
private static readonly JsonSerializerOptions _jsonSerializerOptions = new ()
|
||||
{
|
||||
ReadCommentHandling = JsonCommentHandling.Disallow,
|
||||
WriteIndented = false,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
NumberHandling = JsonNumberHandling.AllowReadingFromString,
|
||||
|
||||
Converters =
|
||||
{
|
||||
new JsonGuidConverter(),
|
||||
new JsonVersionConverter(),
|
||||
new JsonStringEnumConverter(),
|
||||
new JsonNullableStructConverterFactory(),
|
||||
new JsonBoolNumberConverter(),
|
||||
new JsonDateTimeConverter()
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly JsonSerializerOptions _pascalCaseJsonSerializerOptions = new (_jsonSerializerOptions)
|
||||
{
|
||||
PropertyNamingPolicy = null
|
||||
};
|
||||
|
||||
private static readonly JsonSerializerOptions _camelCaseJsonSerializerOptions = new (_jsonSerializerOptions)
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default <see cref="JsonSerializerOptions" /> options.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When changing these options, update
|
||||
/// Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
|
||||
/// -> AddJellyfinApi
|
||||
/// -> AddJsonOptions.
|
||||
/// The return value must not be modified.
|
||||
/// If the defaults must be modified the author must use the copy constructor.
|
||||
/// </remarks>
|
||||
/// <returns>The default <see cref="JsonSerializerOptions" /> options.</returns>
|
||||
public static JsonSerializerOptions GetOptions()
|
||||
{
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
ReadCommentHandling = JsonCommentHandling.Disallow,
|
||||
WriteIndented = false,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
NumberHandling = JsonNumberHandling.AllowReadingFromString,
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
options.Converters.Add(new JsonGuidConverter());
|
||||
options.Converters.Add(new JsonVersionConverter());
|
||||
options.Converters.Add(new JsonStringEnumConverter());
|
||||
options.Converters.Add(new JsonNullableStructConverterFactory());
|
||||
options.Converters.Add(new JsonBoolNumberConverter());
|
||||
|
||||
return options;
|
||||
}
|
||||
=> _jsonSerializerOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Gets camelCase json options.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The return value must not be modified.
|
||||
/// If the defaults must be modified the author must use the copy constructor.
|
||||
/// </remarks>
|
||||
/// <returns>The camelCase <see cref="JsonSerializerOptions" /> options.</returns>
|
||||
public static JsonSerializerOptions GetCamelCaseOptions()
|
||||
{
|
||||
var options = GetOptions();
|
||||
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
|
||||
return options;
|
||||
}
|
||||
=> _camelCaseJsonSerializerOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Gets PascalCase json options.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The return value must not be modified.
|
||||
/// If the defaults must be modified the author must use the copy constructor.
|
||||
/// </remarks>
|
||||
/// <returns>The PascalCase <see cref="JsonSerializerOptions" /> options.</returns>
|
||||
public static JsonSerializerOptions GetPascalCaseOptions()
|
||||
{
|
||||
var options = GetOptions();
|
||||
options.PropertyNamingPolicy = null;
|
||||
return options;
|
||||
}
|
||||
=> _pascalCaseJsonSerializerOptions;
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
|
||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@ -21,7 +22,6 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -17,9 +17,10 @@ namespace MediaBrowser.Controller.Channels
|
||||
{
|
||||
public override bool IsVisible(User user)
|
||||
{
|
||||
if (user.GetPreference(PreferenceKind.BlockedChannels) != null)
|
||||
var blockedChannelsPreference = user.GetPreferenceValues<Guid>(PreferenceKind.BlockedChannels);
|
||||
if (blockedChannelsPreference.Length != 0)
|
||||
{
|
||||
if (user.GetPreference(PreferenceKind.BlockedChannels).Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase))
|
||||
if (blockedChannelsPreference.Contains(Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -27,8 +28,7 @@ namespace MediaBrowser.Controller.Channels
|
||||
else
|
||||
{
|
||||
if (!user.HasPermission(PermissionKind.EnableAllChannels)
|
||||
&& !user.GetPreference(PreferenceKind.EnabledChannels)
|
||||
.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase))
|
||||
&& !user.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels).Contains(Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ namespace MediaBrowser.Controller.Entities.Audio
|
||||
|
||||
protected override bool GetBlockUnratedValue(User user)
|
||||
{
|
||||
return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Music.ToString());
|
||||
return user.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Music);
|
||||
}
|
||||
|
||||
public override UnratedItem GetBlockUnratedType()
|
||||
|
@ -145,7 +145,7 @@ namespace MediaBrowser.Controller.Entities.Audio
|
||||
|
||||
protected override bool GetBlockUnratedValue(User user)
|
||||
{
|
||||
return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Music.ToString());
|
||||
return user.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Music);
|
||||
}
|
||||
|
||||
public override UnratedItem GetBlockUnratedType()
|
||||
|
@ -480,11 +480,11 @@ namespace MediaBrowser.Controller.Entities
|
||||
return true;
|
||||
}
|
||||
|
||||
var allowed = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders);
|
||||
var allowed = user.GetPreferenceValues<Guid>(PreferenceKind.EnableContentDeletionFromFolders);
|
||||
|
||||
if (SourceType == SourceType.Channel)
|
||||
{
|
||||
return allowed.Contains(ChannelId.ToString(""), StringComparer.OrdinalIgnoreCase);
|
||||
return allowed.Contains(ChannelId);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -492,7 +492,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
foreach (var folder in collectionFolders)
|
||||
{
|
||||
if (allowed.Contains(folder.Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase))
|
||||
if (allowed.Contains(folder.Id))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -1909,7 +1909,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
return false;
|
||||
}
|
||||
|
||||
return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(GetBlockUnratedType().ToString());
|
||||
return user.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems).Contains(GetBlockUnratedType());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -186,13 +186,10 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
if (this is ICollectionFolder && !(this is BasePluginFolder))
|
||||
{
|
||||
var blockedMediaFolders = user.GetPreference(PreferenceKind.BlockedMediaFolders);
|
||||
var blockedMediaFolders = user.GetPreferenceValues<Guid>(PreferenceKind.BlockedMediaFolders);
|
||||
if (blockedMediaFolders.Length > 0)
|
||||
{
|
||||
if (blockedMediaFolders.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase) ||
|
||||
|
||||
// Backwards compatibility
|
||||
blockedMediaFolders.Contains(Name, StringComparer.OrdinalIgnoreCase))
|
||||
if (blockedMediaFolders.Contains(Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -200,8 +197,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
else
|
||||
{
|
||||
if (!user.HasPermission(PermissionKind.EnableAllFolders)
|
||||
&& !user.GetPreference(PreferenceKind.EnabledFolders)
|
||||
.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase))
|
||||
&& !user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders).Contains(Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||
|
||||
protected override bool GetBlockUnratedValue(User user)
|
||||
{
|
||||
return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Movie.ToString());
|
||||
return user.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Movie);
|
||||
}
|
||||
|
||||
public override double GetDefaultPrimaryImageAspectRatio()
|
||||
|
@ -452,7 +452,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
|
||||
protected override bool GetBlockUnratedValue(User user)
|
||||
{
|
||||
return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Series.ToString());
|
||||
return user.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Series);
|
||||
}
|
||||
|
||||
public override UnratedItem GetBlockUnratedType()
|
||||
|
@ -1127,13 +1127,25 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
targetVideoCodec = "hevc";
|
||||
}
|
||||
|
||||
var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault();
|
||||
profile = Regex.Replace(profile, @"\s+", String.Empty);
|
||||
var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty;
|
||||
profile = Regex.Replace(profile, @"\s+", string.Empty);
|
||||
|
||||
// We only transcode to HEVC 8-bit for now, force Main Profile.
|
||||
if (profile.Contains("main 10", StringComparison.OrdinalIgnoreCase)
|
||||
|| profile.Contains("main still", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
profile = "main";
|
||||
}
|
||||
|
||||
// Extended Profile is not supported by any known h264 encoders, force Main Profile.
|
||||
if (profile.Contains("extended", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
profile = "main";
|
||||
}
|
||||
|
||||
// Only libx264 support encoding H264 High 10 Profile, otherwise force High Profile.
|
||||
if (!string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
|
||||
&& profile != null
|
||||
&& profile.IndexOf("high 10", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
&& profile.Contains("high 10", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
profile = "high";
|
||||
}
|
||||
@ -1141,8 +1153,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
// h264_vaapi does not support Baseline profile, force Constrained Baseline in this case,
|
||||
// which is compatible (and ugly).
|
||||
if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
|
||||
&& profile != null
|
||||
&& profile.IndexOf("baseline", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
&& profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
profile = "constrained_baseline";
|
||||
}
|
||||
@ -1151,16 +1162,24 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
|
||||
&& profile != null
|
||||
&& profile.IndexOf("baseline", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
&& profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
profile = "baseline";
|
||||
}
|
||||
|
||||
// libx264, h264_qsv, h264_nvenc and h264_vaapi does not support Constrained High profile, force High in this case.
|
||||
if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
&& profile.Contains("high", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
profile = "high";
|
||||
}
|
||||
|
||||
// Currently hevc_amf only support encoding HEVC Main Profile, otherwise force Main Profile.
|
||||
if (!string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
|
||||
&& profile != null
|
||||
&& profile.IndexOf("main 10", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
|
||||
&& profile.Contains("main 10", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
profile = "main";
|
||||
}
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BDInfo" Version="0.7.6.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
|
||||
<PackageReference Include="UTF.Unknown" Version="2.3.0" />
|
||||
</ItemGroup>
|
||||
|
@ -49,7 +49,7 @@ namespace MediaBrowser.Model.Entities
|
||||
}
|
||||
|
||||
instance.ProviderIds.TryGetValue(name, out string? id);
|
||||
return id;
|
||||
return string.IsNullOrEmpty(id) ? null : id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -33,7 +33,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="System.Globalization" Version="4.3.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="5.0.0" />
|
||||
|
@ -22,7 +22,6 @@
|
||||
<PackageReference Include="OptimizedPriorityQueue" Version="5.0.0" />
|
||||
<PackageReference Include="PlaylistsNET" Version="1.1.3" />
|
||||
<PackageReference Include="TMDbLib" Version="1.7.3-alpha" />
|
||||
<PackageReference Include="TvDbSharper" Version="3.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
@ -1,10 +1,16 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Providers.Manager;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -13,14 +19,27 @@ namespace MediaBrowser.Providers.TV
|
||||
{
|
||||
public class SeriesMetadataService : MetadataService<Series, SeriesInfo>
|
||||
{
|
||||
private readonly ILocalizationManager _localizationManager;
|
||||
|
||||
public SeriesMetadataService(
|
||||
IServerConfigurationManager serverConfigurationManager,
|
||||
ILogger<SeriesMetadataService> logger,
|
||||
IProviderManager providerManager,
|
||||
IFileSystem fileSystem,
|
||||
ILibraryManager libraryManager)
|
||||
ILibraryManager libraryManager,
|
||||
ILocalizationManager localizationManager)
|
||||
: base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager)
|
||||
{
|
||||
_localizationManager = localizationManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task AfterMetadataRefresh(Series item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
await base.AfterMetadataRefresh(item, refreshOptions, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
RemoveObsoleteSeasons(item);
|
||||
await FillInMissingSeasonsAsync(item, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -62,5 +81,117 @@ namespace MediaBrowser.Providers.TV
|
||||
targetItem.AirDays = sourceItem.AirDays;
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveObsoleteSeasons(Series series)
|
||||
{
|
||||
// TODO Legacy. It's not really "physical" seasons as any virtual seasons are always converted to non-virtual in FillInMissingSeasonsAsync.
|
||||
var physicalSeasonNumbers = new HashSet<int>();
|
||||
var virtualSeasons = new List<Season>();
|
||||
foreach (var existingSeason in series.Children.OfType<Season>())
|
||||
{
|
||||
if (existingSeason.LocationType != LocationType.Virtual && existingSeason.IndexNumber.HasValue)
|
||||
{
|
||||
physicalSeasonNumbers.Add(existingSeason.IndexNumber.Value);
|
||||
}
|
||||
else if (existingSeason.LocationType == LocationType.Virtual)
|
||||
{
|
||||
virtualSeasons.Add(existingSeason);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var virtualSeason in virtualSeasons)
|
||||
{
|
||||
var seasonNumber = virtualSeason.IndexNumber;
|
||||
// If there's a physical season with the same number or no episodes in the season, delete it
|
||||
if ((seasonNumber.HasValue && physicalSeasonNumbers.Contains(seasonNumber.Value))
|
||||
|| !virtualSeason.GetEpisodes().Any())
|
||||
{
|
||||
Logger.LogInformation("Removing virtual season {SeasonNumber} in series {SeriesName}", virtualSeason.IndexNumber, series.Name);
|
||||
|
||||
LibraryManager.DeleteItem(
|
||||
virtualSeason,
|
||||
new DeleteOptions
|
||||
{
|
||||
DeleteFileLocation = true
|
||||
},
|
||||
false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates seasons for all episodes that aren't in a season folder.
|
||||
/// If no season number can be determined, a dummy season will be created.
|
||||
/// </summary>
|
||||
/// <param name="series">The series.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The async task.</returns>
|
||||
private async Task FillInMissingSeasonsAsync(Series series, CancellationToken cancellationToken)
|
||||
{
|
||||
var episodesInSeriesFolder = series.GetRecursiveChildren(i => i is Episode)
|
||||
.Cast<Episode>()
|
||||
.Where(i => !i.IsInSeasonFolder);
|
||||
|
||||
List<Season> seasons = series.Children.OfType<Season>().ToList();
|
||||
|
||||
// Loop through the unique season numbers
|
||||
foreach (var episode in episodesInSeriesFolder)
|
||||
{
|
||||
// Null season numbers will have a 'dummy' season created because seasons are always required.
|
||||
var seasonNumber = episode.ParentIndexNumber >= 0 ? episode.ParentIndexNumber : null;
|
||||
var existingSeason = seasons.FirstOrDefault(i => i.IndexNumber == seasonNumber);
|
||||
|
||||
if (existingSeason == null)
|
||||
{
|
||||
var season = await CreateSeasonAsync(series, seasonNumber, cancellationToken).ConfigureAwait(false);
|
||||
seasons.Add(season);
|
||||
}
|
||||
else if (existingSeason.IsVirtualItem)
|
||||
{
|
||||
existingSeason.IsVirtualItem = false;
|
||||
await existingSeason.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new season, adds it to the database by linking it to the [series] and refreshes the metadata.
|
||||
/// </summary>
|
||||
/// <param name="series">The series.</param>
|
||||
/// <param name="seasonNumber">The season number.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The newly created season.</returns>
|
||||
private async Task<Season> CreateSeasonAsync(
|
||||
Series series,
|
||||
int? seasonNumber,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
string seasonName = seasonNumber switch
|
||||
{
|
||||
null => _localizationManager.GetLocalizedString("NameSeasonUnknown"),
|
||||
0 => LibraryManager.GetLibraryOptions(series).SeasonZeroDisplayName,
|
||||
_ => string.Format(CultureInfo.InvariantCulture, _localizationManager.GetLocalizedString("NameSeasonNumber"), seasonNumber.Value)
|
||||
};
|
||||
|
||||
Logger.LogInformation("Creating Season {SeasonName} entry for {SeriesName}", seasonName, series.Name);
|
||||
|
||||
var season = new Season
|
||||
{
|
||||
Name = seasonName,
|
||||
IndexNumber = seasonNumber,
|
||||
Id = LibraryManager.GetNewItemId(
|
||||
series.Id + (seasonNumber ?? -1).ToString(CultureInfo.InvariantCulture) + seasonName,
|
||||
typeof(Season)),
|
||||
IsVirtualItem = false,
|
||||
SeriesId = series.Id,
|
||||
SeriesName = series.Name
|
||||
};
|
||||
|
||||
series.AddChild(season, cancellationToken);
|
||||
|
||||
await season.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(FileSystem)), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return season;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
31
deployment/Dockerfile.linux.amd64-musl
Normal file
31
deployment/Dockerfile.linux.amd64-musl
Normal file
@ -0,0 +1,31 @@
|
||||
FROM debian:10
|
||||
# Docker build arguments
|
||||
ARG SOURCE_DIR=/jellyfin
|
||||
ARG ARTIFACT_DIR=/dist
|
||||
ARG SDK_VERSION=5.0
|
||||
# Docker run environment
|
||||
ENV SOURCE_DIR=/jellyfin
|
||||
ENV ARTIFACT_DIR=/dist
|
||||
ENV DEB_BUILD_OPTIONS=noddebs
|
||||
ENV ARCH=amd64
|
||||
ENV IS_DOCKER=YES
|
||||
|
||||
# Prepare Debian build environment
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
|
||||
|
||||
# Install dotnet repository
|
||||
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/a0487784-534a-4912-a4dd-017382083865/be16057043a8f7b6f08c902dc48dd677/dotnet-sdk-5.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
&& mkdir -p dotnet-sdk \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||
|
||||
# Link to docker-build script
|
||||
RUN ln -sf ${SOURCE_DIR}/deployment/build.linux.amd64-musl /build.sh
|
||||
|
||||
VOLUME ${SOURCE_DIR}/
|
||||
|
||||
VOLUME ${ARTIFACT_DIR}/
|
||||
|
||||
ENTRYPOINT ["/build.sh"]
|
31
deployment/Dockerfile.linux.arm64
Normal file
31
deployment/Dockerfile.linux.arm64
Normal file
@ -0,0 +1,31 @@
|
||||
FROM debian:10
|
||||
# Docker build arguments
|
||||
ARG SOURCE_DIR=/jellyfin
|
||||
ARG ARTIFACT_DIR=/dist
|
||||
ARG SDK_VERSION=5.0
|
||||
# Docker run environment
|
||||
ENV SOURCE_DIR=/jellyfin
|
||||
ENV ARTIFACT_DIR=/dist
|
||||
ENV DEB_BUILD_OPTIONS=noddebs
|
||||
ENV ARCH=arm64
|
||||
ENV IS_DOCKER=YES
|
||||
|
||||
# Prepare Debian build environment
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
|
||||
|
||||
# Install dotnet repository
|
||||
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/a0487784-534a-4912-a4dd-017382083865/be16057043a8f7b6f08c902dc48dd677/dotnet-sdk-5.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
&& mkdir -p dotnet-sdk \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||
|
||||
# Link to docker-build script
|
||||
RUN ln -sf ${SOURCE_DIR}/deployment/build.linux.arm64 /build.sh
|
||||
|
||||
VOLUME ${SOURCE_DIR}/
|
||||
|
||||
VOLUME ${ARTIFACT_DIR}/
|
||||
|
||||
ENTRYPOINT ["/build.sh"]
|
31
deployment/Dockerfile.linux.armhf
Normal file
31
deployment/Dockerfile.linux.armhf
Normal file
@ -0,0 +1,31 @@
|
||||
FROM debian:10
|
||||
# Docker build arguments
|
||||
ARG SOURCE_DIR=/jellyfin
|
||||
ARG ARTIFACT_DIR=/dist
|
||||
ARG SDK_VERSION=5.0
|
||||
# Docker run environment
|
||||
ENV SOURCE_DIR=/jellyfin
|
||||
ENV ARTIFACT_DIR=/dist
|
||||
ENV DEB_BUILD_OPTIONS=noddebs
|
||||
ENV ARCH=armhf
|
||||
ENV IS_DOCKER=YES
|
||||
|
||||
# Prepare Debian build environment
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
|
||||
|
||||
# Install dotnet repository
|
||||
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/a0487784-534a-4912-a4dd-017382083865/be16057043a8f7b6f08c902dc48dd677/dotnet-sdk-5.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
&& mkdir -p dotnet-sdk \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||
|
||||
# Link to docker-build script
|
||||
RUN ln -sf ${SOURCE_DIR}/deployment/build.linux.armhf /build.sh
|
||||
|
||||
VOLUME ${SOURCE_DIR}/
|
||||
|
||||
VOLUME ${ARTIFACT_DIR}/
|
||||
|
||||
ENTRYPOINT ["/build.sh"]
|
31
deployment/build.linux.amd64-musl
Executable file
31
deployment/build.linux.amd64-musl
Executable file
@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
|
||||
#= Generic Linux amd64-musl .tar.gz
|
||||
|
||||
set -o errexit
|
||||
set -o xtrace
|
||||
|
||||
# Move to source directory
|
||||
pushd ${SOURCE_DIR}
|
||||
|
||||
# Get version
|
||||
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
|
||||
version="${BUILD_ID}"
|
||||
else
|
||||
version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
|
||||
fi
|
||||
|
||||
# Build archives
|
||||
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-musl-x64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
|
||||
tar -czf jellyfin-server_${version}_linux-amd64-musl.tar.gz -C dist jellyfin-server_${version}
|
||||
rm -rf dist/jellyfin-server_${version}
|
||||
|
||||
# Move the artifacts out
|
||||
mkdir -p ${ARTIFACT_DIR}/
|
||||
mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
|
||||
|
||||
if [[ ${IS_DOCKER} == YES ]]; then
|
||||
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
|
||||
fi
|
||||
|
||||
popd
|
31
deployment/build.linux.arm64
Executable file
31
deployment/build.linux.arm64
Executable file
@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
|
||||
#= Generic Linux arm64 .tar.gz
|
||||
|
||||
set -o errexit
|
||||
set -o xtrace
|
||||
|
||||
# Move to source directory
|
||||
pushd ${SOURCE_DIR}
|
||||
|
||||
# Get version
|
||||
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
|
||||
version="${BUILD_ID}"
|
||||
else
|
||||
version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
|
||||
fi
|
||||
|
||||
# Build archives
|
||||
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-arm64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
|
||||
tar -czf jellyfin-server_${version}_linux-arm64.tar.gz -C dist jellyfin-server_${version}
|
||||
rm -rf dist/jellyfin-server_${version}
|
||||
|
||||
# Move the artifacts out
|
||||
mkdir -p ${ARTIFACT_DIR}/
|
||||
mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
|
||||
|
||||
if [[ ${IS_DOCKER} == YES ]]; then
|
||||
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
|
||||
fi
|
||||
|
||||
popd
|
31
deployment/build.linux.armhf
Executable file
31
deployment/build.linux.armhf
Executable file
@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
|
||||
#= Generic Linux armhf .tar.gz
|
||||
|
||||
set -o errexit
|
||||
set -o xtrace
|
||||
|
||||
# Move to source directory
|
||||
pushd ${SOURCE_DIR}
|
||||
|
||||
# Get version
|
||||
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
|
||||
version="${BUILD_ID}"
|
||||
else
|
||||
version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
|
||||
fi
|
||||
|
||||
# Build archives
|
||||
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-arm --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
|
||||
tar -czf jellyfin-server_${version}_linux-armhf.tar.gz -C dist jellyfin-server_${version}
|
||||
rm -rf dist/jellyfin-server_${version}
|
||||
|
||||
# Move the artifacts out
|
||||
mkdir -p ${ARTIFACT_DIR}/
|
||||
mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
|
||||
|
||||
if [[ ${IS_DOCKER} == YES ]]; then
|
||||
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
|
||||
fi
|
||||
|
||||
popd
|
Loading…
Reference in New Issue
Block a user