aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ci/azure-pipelines-package.yml6
-rw-r--r--Emby.Dlna/Profiles/SonyPs3Profile.cs4
-rw-r--r--Emby.Dlna/Profiles/SonyPs4Profile.cs4
-rw-r--r--Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml4
-rw-r--r--Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml4
-rw-r--r--Emby.Server.Implementations/Library/UserViewManager.cs12
-rw-r--r--Emby.Server.Implementations/Localization/Core/en-GB.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/ja.json7
-rw-r--r--Emby.Server.Implementations/Localization/Core/kk.json19
-rw-r--r--Emby.Server.Implementations/Localization/Core/nl.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/ru.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/sr.json5
-rw-r--r--Emby.Server.Implementations/TV/TVSeriesManager.cs3
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs16
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs2
-rw-r--r--Jellyfin.Data/Entities/User.cs56
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs18
-rw-r--r--Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs4
-rw-r--r--MediaBrowser.Controller/Channels/Channel.cs8
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicArtist.cs2
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs8
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs10
-rw-r--r--MediaBrowser.Controller/Entities/Movies/BoxSet.cs2
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs2
-rw-r--r--deployment/Dockerfile.linux.amd64-musl31
-rw-r--r--deployment/Dockerfile.linux.arm6431
-rw-r--r--deployment/Dockerfile.linux.armhf31
-rwxr-xr-xdeployment/build.linux.amd64-musl31
-rwxr-xr-xdeployment/build.linux.arm6431
-rwxr-xr-xdeployment/build.linux.armhf31
31 files changed, 328 insertions, 67 deletions
diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml
index 47477ba60..606385116 100644
--- a/.ci/azure-pipelines-package.yml
+++ b/.ci/azure-pipelines-package.yml
@@ -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:
diff --git a/Emby.Dlna/Profiles/SonyPs3Profile.cs b/Emby.Dlna/Profiles/SonyPs3Profile.cs
index d56b1df50..e4a7a3a59 100644
--- a/Emby.Dlna/Profiles/SonyPs3Profile.cs
+++ b/Emby.Dlna/Profiles/SonyPs3Profile.cs
@@ -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
diff --git a/Emby.Dlna/Profiles/SonyPs4Profile.cs b/Emby.Dlna/Profiles/SonyPs4Profile.cs
index db56094e2..985df0c9a 100644
--- a/Emby.Dlna/Profiles/SonyPs4Profile.cs
+++ b/Emby.Dlna/Profiles/SonyPs4Profile.cs
@@ -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
diff --git a/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml b/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml
index bafa44b82..129b188e2 100644
--- a/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml
+++ b/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml
@@ -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>
diff --git a/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml b/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml
index eb8e645b3..592119305 100644
--- a/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml
+++ b/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml
@@ -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>
diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs
index e4221dd50..b6b7ea949 100644
--- a/Emby.Server.Implementations/Library/UserViewManager.cs
+++ b/Emby.Server.Implementations/Library/UserViewManager.cs
@@ -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();
}
diff --git a/Emby.Server.Implementations/Localization/Core/en-GB.json b/Emby.Server.Implementations/Localization/Core/en-GB.json
index cd64cdde4..7667612b9 100644
--- a/Emby.Server.Implementations/Localization/Core/en-GB.json
+++ b/Emby.Server.Implementations/Localization/Core/en-GB.json
@@ -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"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json
index 02bf8496f..fa0ab8b92 100644
--- a/Emby.Server.Implementations/Localization/Core/ja.json
+++ b/Emby.Server.Implementations/Localization/Core/ja.json
@@ -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": "デフォルト"
}
diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json
index 91c1fb15b..47c0879be 100644
--- a/Emby.Server.Implementations/Localization/Core/kk.json
+++ b/Emby.Server.Implementations/Localization/Core/kk.json
@@ -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"
}
diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json
index b6672a554..1e80d0b5f 100644
--- a/Emby.Server.Implementations/Localization/Core/nl.json
+++ b/Emby.Server.Implementations/Localization/Core/nl.json
@@ -1,7 +1,7 @@
{
"Albums": "Albums",
"AppDeviceValues": "App: {0}, Apparaat: {1}",
- "Application": "Programma",
+ "Application": "Applicatie",
"Artists": "Artiesten",
"AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd",
"Books": "Boeken",
diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json
index ca6172fce..03d30247a 100644
--- a/Emby.Server.Implementations/Localization/Core/ru.json
+++ b/Emby.Server.Implementations/Localization/Core/ru.json
@@ -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": "Создаются эскизы для видео, которые содержат сцены.",
diff --git a/Emby.Server.Implementations/Localization/Core/sr.json b/Emby.Server.Implementations/Localization/Core/sr.json
index 8da92f309..2984ed972 100644
--- a/Emby.Server.Implementations/Localization/Core/sr.json
+++ b/Emby.Server.Implementations/Localization/Core/sr.json
@@ -114,5 +114,8 @@
"TasksLibraryCategory": "Библиотека",
"TasksMaintenanceCategory": "Одржавање",
"TaskCleanActivityLogDescription": "Брише историју активности старију од конфигурисаног броја година.",
- "TaskCleanActivityLog": "Очисти историју активности"
+ "TaskCleanActivityLog": "Очисти историју активности",
+ "Undefined": "Недефинисано",
+ "Forced": "Форсирано",
+ "Default": "Подразумевано"
}
diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs
index 447c587f9..f0734340b 100644
--- a/Emby.Server.Implementations/TV/TVSeriesManager.cs
+++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs
@@ -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();
}
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index 7e9035f80..b84136ac6 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -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();
}
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index 184843b39..c1538a431 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -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>
diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs
index 0fd8cb224..362f3b4eb 100644
--- a/Jellyfin.Data/Entities/User.cs
+++ b/Jellyfin.Data/Entities/User.cs
@@ -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;
@@ -414,6 +414,44 @@ namespace Jellyfin.Data.Entities
}
/// <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>
/// <param name="preference">The preference kind.</param>
@@ -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)
diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs
index b76b272cf..d1de5408c 100644
--- a/Jellyfin.Server.Implementations/Users/UserManager.cs
+++ b/Jellyfin.Server.Implementations/Users/UserManager.cs
@@ -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);
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
index 74c550331..33f039c39 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
@@ -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);
diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs
index 129cdb519..b2315bda4 100644
--- a/MediaBrowser.Controller/Channels/Channel.cs
+++ b/MediaBrowser.Controller/Channels/Channel.cs
@@ -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;
}
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
index 48cd9371a..9a33ad9d7 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
@@ -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()
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
index c5e50cf45..8a9bb12c7 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
@@ -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()
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 1b25fbdbb..cbb02aabd 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -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>
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 57d04ddfa..cac5026f7 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -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;
}
diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
index 8de88cc1b..05e4229ca 100644
--- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
+++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
@@ -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()
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index e8afa9a49..1a379074d 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -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()
diff --git a/deployment/Dockerfile.linux.amd64-musl b/deployment/Dockerfile.linux.amd64-musl
new file mode 100644
index 000000000..08b4ffa52
--- /dev/null
+++ b/deployment/Dockerfile.linux.amd64-musl
@@ -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"]
diff --git a/deployment/Dockerfile.linux.arm64 b/deployment/Dockerfile.linux.arm64
new file mode 100644
index 000000000..b8499c917
--- /dev/null
+++ b/deployment/Dockerfile.linux.arm64
@@ -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"]
diff --git a/deployment/Dockerfile.linux.armhf b/deployment/Dockerfile.linux.armhf
new file mode 100644
index 000000000..80c4d1469
--- /dev/null
+++ b/deployment/Dockerfile.linux.armhf
@@ -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"]
diff --git a/deployment/build.linux.amd64-musl b/deployment/build.linux.amd64-musl
new file mode 100755
index 000000000..72444c05e
--- /dev/null
+++ b/deployment/build.linux.amd64-musl
@@ -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
diff --git a/deployment/build.linux.arm64 b/deployment/build.linux.arm64
new file mode 100755
index 000000000..e362607a7
--- /dev/null
+++ b/deployment/build.linux.arm64
@@ -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
diff --git a/deployment/build.linux.armhf b/deployment/build.linux.armhf
new file mode 100755
index 000000000..c0d0607fc
--- /dev/null
+++ b/deployment/build.linux.armhf
@@ -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