diff --git a/Directory.Packages.props b/Directory.Packages.props
index 38cf513124..bf6f9eaebd 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -46,6 +46,7 @@
+
@@ -53,6 +54,8 @@
+
+
@@ -80,6 +83,7 @@
+
diff --git a/Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs b/Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs
index 932f9d6250..abdd5ec833 100644
--- a/Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs
+++ b/Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs
@@ -6,12 +6,14 @@ using System.Reflection;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.DbConfiguration;
using Jellyfin.Database.Implementations.Locking;
+using Jellyfin.Database.Providers.PostgreSQL;
using Jellyfin.Database.Providers.Sqlite;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Npgsql;
using JellyfinDbProviderFactory = System.Func;
namespace Jellyfin.Server.Implementations.Extensions;
@@ -24,6 +26,13 @@ public static class ServiceCollectionExtensions
private static IEnumerable DatabaseProviderTypes()
{
yield return typeof(SqliteDatabaseProvider);
+ yield return typeof(PostgreSqlDatabaseProvider);
+ }
+
+ private static int GetPoolOption(IEnumerable? options, string key, int defaultValue)
+ {
+ var value = options?.FirstOrDefault(o => o.Key.Equals(key, StringComparison.OrdinalIgnoreCase))?.Value;
+ return int.TryParse(value, out var parsed) ? parsed : defaultValue;
}
private static IDictionary GetSupportedDbProviders()
@@ -123,6 +132,50 @@ public static class ServiceCollectionExtensions
serviceCollection.AddSingleton(providerFactory!);
+ if (efCoreConfiguration.DatabaseType.Equals("Jellyfin-PostgreSQL", StringComparison.OrdinalIgnoreCase))
+ {
+ serviceCollection.AddSingleton(static sp =>
+ {
+ var config = sp.GetRequiredService().GetConfiguration("database");
+ var options = config.CustomProviderOptions?.Options;
+
+ var connectionString =
+ Environment.GetEnvironmentVariable("POSTGRES_CONNECTION_STRING")
+ ?? options
+ ?.FirstOrDefault(o => o.Key.Equals("ConnectionString", StringComparison.OrdinalIgnoreCase))
+ ?.Value
+ ?? config.CustomProviderOptions?.ConnectionString
+ ?? throw new InvalidOperationException(
+ "No PostgreSQL connection string found. Set the POSTGRES_CONNECTION_STRING environment variable, " +
+ "or provide it via CustomProviderOptions.Options[\"ConnectionString\"] or CustomProviderOptions.ConnectionString.");
+
+ // Support postgresql:// / postgres:// URI format (e.g. DATABASE_URL convention).
+ // NpgsqlDataSourceBuilder requires ADO.NET key=value format; convert if needed.
+ if (connectionString.StartsWith("postgresql://", StringComparison.OrdinalIgnoreCase)
+ || connectionString.StartsWith("postgres://", StringComparison.OrdinalIgnoreCase))
+ {
+ var uri = new Uri(connectionString);
+ var userInfoParts = uri.UserInfo.Split(':', 2);
+ connectionString = new NpgsqlConnectionStringBuilder
+ {
+ Host = uri.Host,
+ Port = uri.Port > 0 ? uri.Port : 5432,
+ Database = uri.AbsolutePath.TrimStart('/'),
+ Username = userInfoParts.Length > 0 ? Uri.UnescapeDataString(userInfoParts[0]) : null,
+ Password = userInfoParts.Length > 1 ? Uri.UnescapeDataString(userInfoParts[1]) : null,
+ }.ToString();
+ }
+
+ var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
+
+ dataSourceBuilder.ConnectionStringBuilder.MinPoolSize = GetPoolOption(options, "MinPoolSize", 2);
+ dataSourceBuilder.ConnectionStringBuilder.MaxPoolSize = GetPoolOption(options, "MaxPoolSize", 20);
+ dataSourceBuilder.ConnectionStringBuilder.CommandTimeout = GetPoolOption(options, "CommandTimeout", 30);
+
+ return dataSourceBuilder.Build();
+ });
+ }
+
switch (efCoreConfiguration.LockingBehavior)
{
case DatabaseLockingBehaviorTypes.NoLock:
diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
index 4f0c377229..a9229cae25 100644
--- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
+++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
@@ -35,6 +35,7 @@
+
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index 35eaff6532..8390c91313 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -24,6 +24,7 @@ using Jellyfin.Server.ServerSetupApp;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
@@ -304,6 +305,10 @@ namespace Jellyfin.Server
.AddJellyfinDbContext(startupConfigurationManager, startupConfig)
.AddSingleton(appPaths)
.AddSingleton(appPaths)
+ // Required by the NpgsqlDataSource factory in AddJellyfinDbContext when
+ // DatabaseType=Jellyfin-PostgreSQL — the factory resolves this from DI
+ // to read CustomProviderOptions and pool settings.
+ .AddSingleton(startupConfigurationManager)
.RegisterStartupLogger();
var startupService = migrationStartupServiceProvider.BuildServiceProvider();
diff --git a/Jellyfin.sln b/Jellyfin.sln
index b666e4ae16..28ec04f50d 100644
--- a/Jellyfin.sln
+++ b/Jellyfin.sln
@@ -68,6 +68,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "te
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Database.Tests.PostgreSQL", "tests\Jellyfin.Database.Tests.PostgreSQL\Jellyfin.Database.Tests.PostgreSQL.csproj", "{B5F2C3A1-9D4E-4F8A-B123-456789ABCDEF}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Providers.Tests", "tests\Jellyfin.Providers.Tests\Jellyfin.Providers.Tests.csproj", "{A964008C-2136-4716-B6CB-B3426C22320A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}"
@@ -95,6 +97,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Jellyfin.Database", "Jellyf
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Database.Providers.Sqlite", "src\Jellyfin.Database\Jellyfin.Database.Providers.Sqlite\Jellyfin.Database.Providers.Sqlite.csproj", "{A5590358-33CC-4B39-BDE7-DC62FEB03C76}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Database.Providers.PostgreSQL", "src\Jellyfin.Database\Jellyfin.Database.Providers.PostgreSQL\Jellyfin.Database.Providers.PostgreSQL.csproj", "{B3C4D5E6-F7A8-4B9C-0D1E-2F3A4B5C6D7E}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Database.Implementations", "src\Jellyfin.Database\Jellyfin.Database.Implementations\Jellyfin.Database.Implementations.csproj", "{8C9F9221-8415-496C-B1F5-E7756F03FA59}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.CodeAnalysis", "src\Jellyfin.CodeAnalysis\Jellyfin.CodeAnalysis.csproj", "{11643D0F-6761-4EF7-AB71-6F9F8DE00714}"
@@ -219,6 +223,10 @@ Global
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B5F2C3A1-9D4E-4F8A-B123-456789ABCDEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B5F2C3A1-9D4E-4F8A-B123-456789ABCDEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B5F2C3A1-9D4E-4F8A-B123-456789ABCDEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B5F2C3A1-9D4E-4F8A-B123-456789ABCDEF}.Release|Any CPU.Build.0 = Release|Any CPU
{A964008C-2136-4716-B6CB-B3426C22320A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A964008C-2136-4716-B6CB-B3426C22320A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A964008C-2136-4716-B6CB-B3426C22320A}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -259,6 +267,10 @@ Global
{A5590358-33CC-4B39-BDE7-DC62FEB03C76}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A5590358-33CC-4B39-BDE7-DC62FEB03C76}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5590358-33CC-4B39-BDE7-DC62FEB03C76}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B3C4D5E6-F7A8-4B9C-0D1E-2F3A4B5C6D7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B3C4D5E6-F7A8-4B9C-0D1E-2F3A4B5C6D7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B3C4D5E6-F7A8-4B9C-0D1E-2F3A4B5C6D7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B3C4D5E6-F7A8-4B9C-0D1E-2F3A4B5C6D7E}.Release|Any CPU.Build.0 = Release|Any CPU
{8C9F9221-8415-496C-B1F5-E7756F03FA59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C9F9221-8415-496C-B1F5-E7756F03FA59}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C9F9221-8415-496C-B1F5-E7756F03FA59}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -290,6 +302,7 @@ Global
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
+ {B5F2C3A1-9D4E-4F8A-B123-456789ABCDEF} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{A964008C-2136-4716-B6CB-B3426C22320A} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{750B8757-BE3D-4F8C-941A-FBAD94904ADA} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}
{332A5C7A-F907-47CA-910E-BE6F7371B9E0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
@@ -301,6 +314,7 @@ Global
{8C6B2B13-58A4-4506-9DAB-1F882A093FE0} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}
{4C54CE05-69C8-48FA-8785-39F7F6DB1CAD} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}
{A5590358-33CC-4B39-BDE7-DC62FEB03C76} = {4C54CE05-69C8-48FA-8785-39F7F6DB1CAD}
+ {B3C4D5E6-F7A8-4B9C-0D1E-2F3A4B5C6D7E} = {4C54CE05-69C8-48FA-8785-39F7F6DB1CAD}
{8C9F9221-8415-496C-B1F5-E7756F03FA59} = {4C54CE05-69C8-48FA-8785-39F7F6DB1CAD}
{11643D0F-6761-4EF7-AB71-6F9F8DE00714} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}
{E24A279C-9A37-419A-8F9C-853C11FBE753} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
diff --git a/src/Jellyfin.Database/Jellyfin.Database.Providers.PostgreSQL/Jellyfin.Database.Providers.PostgreSQL.csproj b/src/Jellyfin.Database/Jellyfin.Database.Providers.PostgreSQL/Jellyfin.Database.Providers.PostgreSQL.csproj
new file mode 100644
index 0000000000..2d23f99a54
--- /dev/null
+++ b/src/Jellyfin.Database/Jellyfin.Database.Providers.PostgreSQL/Jellyfin.Database.Providers.PostgreSQL.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net10.0
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
diff --git a/src/Jellyfin.Database/Jellyfin.Database.Providers.PostgreSQL/Migrations/20260911134055_InitialPostgreSql.Designer.cs b/src/Jellyfin.Database/Jellyfin.Database.Providers.PostgreSQL/Migrations/20260911134055_InitialPostgreSql.Designer.cs
new file mode 100644
index 0000000000..a2e8b8b505
--- /dev/null
+++ b/src/Jellyfin.Database/Jellyfin.Database.Providers.PostgreSQL/Migrations/20260911134055_InitialPostgreSql.Designer.cs
@@ -0,0 +1,1774 @@
+//
+using System;
+using Jellyfin.Database.Implementations;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Jellyfin.Database.Providers.PostgreSQL.Migrations
+{
+ [DbContext(typeof(JellyfinDbContext))]
+ [Migration("20260911134055_InitialPostgreSql")]
+ partial class InitialPostgreSql
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.11")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.AccessSchedule", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("DayOfWeek")
+ .HasColumnType("integer");
+
+ b.Property("EndHour")
+ .HasColumnType("double precision");
+
+ b.Property("StartHour")
+ .HasColumnType("double precision");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AccessSchedules");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.ActivityLog", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("DateCreated")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ItemId")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("LogSeverity")
+ .HasColumnType("integer");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)");
+
+ b.Property("Overview")
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("bigint");
+
+ b.Property("ShortOverview")
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DateCreated");
+
+ b.ToTable("ActivityLogs");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.AncestorId", b =>
+ {
+ b.Property("ItemId")
+ .HasColumnType("uuid");
+
+ b.Property("ParentItemId")
+ .HasColumnType("uuid");
+
+ b.HasKey("ItemId", "ParentItemId");
+
+ b.HasIndex("ParentItemId");
+
+ b.ToTable("AncestorIds");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.AttachmentStreamInfo", b =>
+ {
+ b.Property("ItemId")
+ .HasColumnType("uuid");
+
+ b.Property("Index")
+ .HasColumnType("integer");
+
+ b.Property("Codec")
+ .HasColumnType("text");
+
+ b.Property("CodecTag")
+ .HasColumnType("text");
+
+ b.Property("Comment")
+ .HasColumnType("text");
+
+ b.Property("Filename")
+ .HasColumnType("text");
+
+ b.Property("MimeType")
+ .HasColumnType("text");
+
+ b.HasKey("ItemId", "Index");
+
+ b.ToTable("AttachmentStreamInfos");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.BaseItemEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Album")
+ .HasColumnType("text");
+
+ b.Property("AlbumArtists")
+ .HasColumnType("text");
+
+ b.Property("Artists")
+ .HasColumnType("text");
+
+ b.Property("Audio")
+ .HasColumnType("integer");
+
+ b.Property("ChannelId")
+ .HasColumnType("uuid");
+
+ b.Property("CleanName")
+ .HasColumnType("text");
+
+ b.Property("CommunityRating")
+ .HasColumnType("real");
+
+ b.Property("CriticRating")
+ .HasColumnType("real");
+
+ b.Property("CustomRating")
+ .HasColumnType("text");
+
+ b.Property("Data")
+ .HasColumnType("text");
+
+ b.Property("DateCreated")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DateLastMediaAdded")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DateLastRefreshed")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DateLastSaved")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DateModified")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("EndDate")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("EpisodeTitle")
+ .HasColumnType("text");
+
+ b.Property("ExternalId")
+ .HasColumnType("text");
+
+ b.Property("ExternalSeriesId")
+ .HasColumnType("text");
+
+ b.Property("ExternalServiceId")
+ .HasColumnType("text");
+
+ b.Property("ExtraType")
+ .HasColumnType("integer");
+
+ b.Property("ForcedSortName")
+ .HasColumnType("text");
+
+ b.Property("Genres")
+ .HasColumnType("text");
+
+ b.Property("Height")
+ .HasColumnType("integer");
+
+ b.Property("IndexNumber")
+ .HasColumnType("integer");
+
+ b.Property("InheritedParentalRatingSubValue")
+ .HasColumnType("integer");
+
+ b.Property("InheritedParentalRatingValue")
+ .HasColumnType("integer");
+
+ b.Property("IsFolder")
+ .HasColumnType("boolean");
+
+ b.Property("IsInMixedFolder")
+ .HasColumnType("boolean");
+
+ b.Property("IsLocked")
+ .HasColumnType("boolean");
+
+ b.Property("IsMovie")
+ .HasColumnType("boolean");
+
+ b.Property("IsRepeat")
+ .HasColumnType("boolean");
+
+ b.Property("IsSeries")
+ .HasColumnType("boolean");
+
+ b.Property("IsVirtualItem")
+ .HasColumnType("boolean");
+
+ b.Property("LUFS")
+ .HasColumnType("real");
+
+ b.Property("MediaType")
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .HasColumnType("text");
+
+ b.Property("NormalizationGain")
+ .HasColumnType("real");
+
+ b.Property("OfficialRating")
+ .HasColumnType("text");
+
+ b.Property("OriginalLanguage")
+ .HasColumnType("text");
+
+ b.Property("OriginalTitle")
+ .HasColumnType("text");
+
+ b.Property("Overview")
+ .HasColumnType("text");
+
+ b.Property("OwnerId")
+ .HasColumnType("uuid");
+
+ b.Property("ParentId")
+ .HasColumnType("uuid");
+
+ b.Property("ParentIndexNumber")
+ .HasColumnType("integer");
+
+ b.Property("Path")
+ .HasColumnType("text");
+
+ b.Property("PreferredMetadataCountryCode")
+ .HasColumnType("text");
+
+ b.Property("PreferredMetadataLanguage")
+ .HasColumnType("text");
+
+ b.Property("PremiereDate")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("PresentationUniqueKey")
+ .HasColumnType("text");
+
+ b.Property("PrimaryVersionId")
+ .HasColumnType("uuid");
+
+ b.Property("ProductionLocations")
+ .HasColumnType("text");
+
+ b.Property("ProductionYear")
+ .HasColumnType("integer");
+
+ b.Property("RunTimeTicks")
+ .HasColumnType("bigint");
+
+ b.Property("SeasonId")
+ .HasColumnType("uuid");
+
+ b.Property("SeasonName")
+ .HasColumnType("text");
+
+ b.Property("SeriesId")
+ .HasColumnType("uuid");
+
+ b.Property("SeriesName")
+ .HasColumnType("text");
+
+ b.Property("SeriesPresentationUniqueKey")
+ .HasColumnType("text");
+
+ b.Property("ShowId")
+ .HasColumnType("text");
+
+ b.Property("Size")
+ .HasColumnType("bigint");
+
+ b.Property("SortName")
+ .HasColumnType("text");
+
+ b.Property("StartDate")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Studios")
+ .HasColumnType("text");
+
+ b.Property("Tagline")
+ .HasColumnType("text");
+
+ b.Property("Tags")
+ .HasColumnType("text");
+
+ b.Property("TopParentId")
+ .HasColumnType("uuid");
+
+ b.Property("TotalBitrate")
+ .HasColumnType("integer");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UnratedType")
+ .HasColumnType("text");
+
+ b.Property("Width")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name");
+
+ b.HasIndex("OwnerId");
+
+ b.HasIndex("ParentId");
+
+ b.HasIndex("Path");
+
+ b.HasIndex("PresentationUniqueKey");
+
+ b.HasIndex("PrimaryVersionId")
+ .HasFilter("\"PrimaryVersionId\" IS NOT NULL");
+
+ b.HasIndex("SeasonId");
+
+ b.HasIndex("SeriesId");
+
+ b.HasIndex("SeriesName");
+
+ b.HasIndex("ExtraType", "OwnerId");
+
+ b.HasIndex("TopParentId", "Id");
+
+ b.HasIndex("Type", "CleanName");
+
+ b.HasIndex("TopParentId", "Type", "IsVirtualItem")
+ .HasFilter("\"PrimaryVersionId\" IS NULL AND (\"OwnerId\" IS NULL OR \"ExtraType\" IS NOT NULL)");
+
+ b.HasIndex("Type", "TopParentId", "Id");
+
+ b.HasIndex("Type", "TopParentId", "PresentationUniqueKey");
+
+ b.HasIndex("Type", "TopParentId", "SortName");
+
+ b.HasIndex("Type", "TopParentId", "StartDate");
+
+ b.HasIndex("MediaType", "TopParentId", "IsVirtualItem", "PresentationUniqueKey");
+
+ b.HasIndex("TopParentId", "IsFolder", "IsVirtualItem", "DateCreated");
+
+ b.HasIndex("TopParentId", "MediaType", "IsVirtualItem", "DateCreated");
+
+ b.HasIndex("TopParentId", "Type", "IsVirtualItem", "DateCreated");
+
+ b.HasIndex("Type", "SeriesPresentationUniqueKey", "IsFolder", "IsVirtualItem");
+
+ b.HasIndex("Type", "SeriesPresentationUniqueKey", "ParentIndexNumber", "IndexNumber");
+
+ b.HasIndex("Type", "SeriesPresentationUniqueKey", "PresentationUniqueKey", "SortName");
+
+ b.HasIndex("IsFolder", "TopParentId", "IsVirtualItem", "PresentationUniqueKey", "DateCreated");
+
+ b.HasIndex("Type", "TopParentId", "IsVirtualItem", "PresentationUniqueKey", "DateCreated");
+
+ b.ToTable("BaseItems");
+
+ b.HasData(
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000001"),
+ IsFolder = false,
+ IsInMixedFolder = false,
+ IsLocked = false,
+ IsMovie = false,
+ IsRepeat = false,
+ IsSeries = false,
+ IsVirtualItem = false,
+ Name = "This is a placeholder item for UserData that has been detached from its original item",
+ Type = "PLACEHOLDER"
+ });
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.BaseItemImageInfo", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Blurhash")
+ .HasColumnType("bytea");
+
+ b.Property("DateModified")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Height")
+ .HasColumnType("integer");
+
+ b.Property("ImageType")
+ .HasColumnType("integer");
+
+ b.Property("ItemId")
+ .HasColumnType("uuid");
+
+ b.Property("Path")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Width")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ItemId", "ImageType");
+
+ b.ToTable("BaseItemImageInfos");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.BaseItemMetadataField", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("integer");
+
+ b.Property("ItemId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id", "ItemId");
+
+ b.HasIndex("ItemId");
+
+ b.ToTable("BaseItemMetadataFields");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.BaseItemProvider", b =>
+ {
+ b.Property("ItemId")
+ .HasColumnType("uuid");
+
+ b.Property("ProviderId")
+ .HasColumnType("text");
+
+ b.Property("ProviderValue")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("ItemId", "ProviderId");
+
+ b.HasIndex("ProviderId", "ItemId", "ProviderValue");
+
+ b.ToTable("BaseItemProviders");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.BaseItemTrailerType", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("integer");
+
+ b.Property("ItemId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id", "ItemId");
+
+ b.HasIndex("ItemId");
+
+ b.ToTable("BaseItemTrailerTypes");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.Chapter", b =>
+ {
+ b.Property("ItemId")
+ .HasColumnType("uuid");
+
+ b.Property("ChapterIndex")
+ .HasColumnType("integer");
+
+ b.Property("ImageDateModified")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ImagePath")
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .HasColumnType("text");
+
+ b.Property("StartPositionTicks")
+ .HasColumnType("bigint");
+
+ b.HasKey("ItemId", "ChapterIndex");
+
+ b.ToTable("Chapters");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.CustomItemDisplayPreferences", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Client")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)");
+
+ b.Property("ItemId")
+ .HasColumnType("uuid");
+
+ b.Property("Key")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.Property("Value")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "ItemId", "Client", "Key")
+ .IsUnique();
+
+ b.ToTable("CustomItemDisplayPreferences");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.DisplayPreferences", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ChromecastVersion")
+ .HasColumnType("integer");
+
+ b.Property("Client")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)");
+
+ b.Property("DashboardTheme")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)");
+
+ b.Property("EnableNextVideoInfoOverlay")
+ .HasColumnType("boolean");
+
+ b.Property("IndexBy")
+ .HasColumnType("integer");
+
+ b.Property("ItemId")
+ .HasColumnType("uuid");
+
+ b.Property("ScrollDirection")
+ .HasColumnType("integer");
+
+ b.Property("ShowBackdrop")
+ .HasColumnType("boolean");
+
+ b.Property("ShowSidebar")
+ .HasColumnType("boolean");
+
+ b.Property("SkipBackwardLength")
+ .HasColumnType("integer");
+
+ b.Property("SkipForwardLength")
+ .HasColumnType("integer");
+
+ b.Property("TvHome")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "ItemId", "Client")
+ .IsUnique();
+
+ b.ToTable("DisplayPreferences");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.HomeSection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("DisplayPreferencesId")
+ .HasColumnType("integer");
+
+ b.Property("Order")
+ .HasColumnType("integer");
+
+ b.Property("Type")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DisplayPreferencesId");
+
+ b.ToTable("HomeSection");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.ImageInfo", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("LastModified")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Path")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("ImageInfos");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.ItemDisplayPreferences", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Client")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)");
+
+ b.Property("IndexBy")
+ .HasColumnType("integer");
+
+ b.Property("ItemId")
+ .HasColumnType("uuid");
+
+ b.Property("RememberIndexing")
+ .HasColumnType("boolean");
+
+ b.Property("RememberSorting")
+ .HasColumnType("boolean");
+
+ b.Property("SortBy")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.Property("ViewType")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("ItemDisplayPreferences");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.ItemValue", b =>
+ {
+ b.Property("ItemValueId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CleanValue")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Type")
+ .HasColumnType("integer");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("ItemValueId");
+
+ b.HasIndex("Type", "CleanValue");
+
+ b.HasIndex("Type", "Value")
+ .IsUnique();
+
+ b.ToTable("ItemValues");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.ItemValueMap", b =>
+ {
+ b.Property("ItemValueId")
+ .HasColumnType("uuid");
+
+ b.Property("ItemId")
+ .HasColumnType("uuid");
+
+ b.HasKey("ItemValueId", "ItemId");
+
+ b.HasIndex("ItemId");
+
+ b.ToTable("ItemValuesMap");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.KeyframeData", b =>
+ {
+ b.Property("ItemId")
+ .HasColumnType("uuid");
+
+ b.PrimitiveCollection("KeyframeTicks")
+ .HasColumnType("bigint[]");
+
+ b.Property("TotalDuration")
+ .HasColumnType("bigint");
+
+ b.HasKey("ItemId");
+
+ b.ToTable("KeyframeData");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.LinkedChildEntity", b =>
+ {
+ b.Property("ParentId")
+ .HasColumnType("uuid");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer");
+
+ b.Property("ChildId")
+ .HasColumnType("uuid");
+
+ b.Property("ChildType")
+ .HasColumnType("integer");
+
+ b.HasKey("ParentId", "SortOrder");
+
+ b.HasIndex("ChildId", "ChildType");
+
+ b.HasIndex("ParentId", "ChildType");
+
+ b.ToTable("LinkedChildren", (string)null);
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.MediaSegment", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("EndTicks")
+ .HasColumnType("bigint");
+
+ b.Property("ItemId")
+ .HasColumnType("uuid");
+
+ b.Property("SegmentProviderId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("StartTicks")
+ .HasColumnType("bigint");
+
+ b.Property("Type")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.ToTable("MediaSegments");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.MediaStreamInfo", b =>
+ {
+ b.Property("ItemId")
+ .HasColumnType("uuid");
+
+ b.Property("StreamIndex")
+ .HasColumnType("integer");
+
+ b.Property("AspectRatio")
+ .HasColumnType("text");
+
+ b.Property("AverageFrameRate")
+ .HasColumnType("real");
+
+ b.Property("BitDepth")
+ .HasColumnType("integer");
+
+ b.Property("BitRate")
+ .HasColumnType("integer");
+
+ b.Property("BlPresentFlag")
+ .HasColumnType("integer");
+
+ b.Property("ChannelLayout")
+ .HasColumnType("text");
+
+ b.Property("Channels")
+ .HasColumnType("integer");
+
+ b.Property("Codec")
+ .HasColumnType("text");
+
+ b.Property("CodecTag")
+ .HasColumnType("text");
+
+ b.Property("CodecTimeBase")
+ .HasColumnType("text");
+
+ b.Property("ColorPrimaries")
+ .HasColumnType("text");
+
+ b.Property("ColorSpace")
+ .HasColumnType("text");
+
+ b.Property("ColorTransfer")
+ .HasColumnType("text");
+
+ b.Property("Comment")
+ .HasColumnType("text");
+
+ b.Property("DvBlSignalCompatibilityId")
+ .HasColumnType("integer");
+
+ b.Property("DvLevel")
+ .HasColumnType("integer");
+
+ b.Property("DvProfile")
+ .HasColumnType("integer");
+
+ b.Property("DvVersionMajor")
+ .HasColumnType("integer");
+
+ b.Property("DvVersionMinor")
+ .HasColumnType("integer");
+
+ b.Property("ElPresentFlag")
+ .HasColumnType("integer");
+
+ b.Property("Hdr10PlusPresentFlag")
+ .HasColumnType("boolean");
+
+ b.Property("Height")
+ .HasColumnType("integer");
+
+ b.Property("IsAnamorphic")
+ .HasColumnType("boolean");
+
+ b.Property("IsAvc")
+ .HasColumnType("boolean");
+
+ b.Property("IsDefault")
+ .HasColumnType("boolean");
+
+ b.Property("IsExternal")
+ .HasColumnType("boolean");
+
+ b.Property("IsForced")
+ .HasColumnType("boolean");
+
+ b.Property("IsHearingImpaired")
+ .HasColumnType("boolean");
+
+ b.Property("IsInterlaced")
+ .HasColumnType("boolean");
+
+ b.Property("IsOriginal")
+ .HasColumnType("boolean");
+
+ b.Property("KeyFrames")
+ .HasColumnType("text");
+
+ b.Property("Language")
+ .HasColumnType("text");
+
+ b.Property("Level")
+ .HasColumnType("real");
+
+ b.Property("NalLengthSize")
+ .HasColumnType("text");
+
+ b.Property("Path")
+ .HasColumnType("text");
+
+ b.Property("PixelFormat")
+ .HasColumnType("text");
+
+ b.Property("Profile")
+ .HasColumnType("text");
+
+ b.Property("RealFrameRate")
+ .HasColumnType("real");
+
+ b.Property("RefFrames")
+ .HasColumnType("integer");
+
+ b.Property("Rotation")
+ .HasColumnType("integer");
+
+ b.Property("RpuPresentFlag")
+ .HasColumnType("integer");
+
+ b.Property("SampleRate")
+ .HasColumnType("integer");
+
+ b.Property("StreamType")
+ .HasColumnType("integer");
+
+ b.Property("TimeBase")
+ .HasColumnType("text");
+
+ b.Property("Title")
+ .HasColumnType("text");
+
+ b.Property("Width")
+ .HasColumnType("integer");
+
+ b.HasKey("ItemId", "StreamIndex");
+
+ b.HasIndex("StreamType", "ItemId", "Language", "IsExternal");
+
+ b.ToTable("MediaStreamInfos");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.People", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("PersonType")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name");
+
+ b.ToTable("Peoples");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.PeopleBaseItemMap", b =>
+ {
+ b.Property("ItemId")
+ .HasColumnType("uuid");
+
+ b.Property("PeopleId")
+ .HasColumnType("uuid");
+
+ b.Property("Role")
+ .HasColumnType("text");
+
+ b.Property("ListOrder")
+ .HasColumnType("integer");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer");
+
+ b.HasKey("ItemId", "PeopleId", "Role");
+
+ b.HasIndex("ItemId", "ListOrder");
+
+ b.HasIndex("ItemId", "SortOrder");
+
+ b.HasIndex("PeopleId", "ItemId");
+
+ b.ToTable("PeopleBaseItemMap");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.Permission", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Kind")
+ .HasColumnType("integer");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("bigint");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.Property("Value")
+ .HasColumnType("boolean");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "Kind")
+ .IsUnique();
+
+ b.ToTable("Permissions");
+ });
+
+ modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.Preference", b =>
+ {
+ b.Property