diff --git a/Emby.Server.Implementations/Data/ItemTypeLookup.cs b/Emby.Server.Implementations/Data/ItemTypeLookup.cs
index 1f73755f5d..b66e7f5d98 100644
--- a/Emby.Server.Implementations/Data/ItemTypeLookup.cs
+++ b/Emby.Server.Implementations/Data/ItemTypeLookup.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Threading.Channels;
using Emby.Server.Implementations.Playlists;
using Jellyfin.Data.Enums;
+using Jellyfin.Server.Implementations;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
@@ -14,19 +15,13 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Data;
-///
-/// Provides static topic based lookups for the BaseItemKind.
-///
+///
public class ItemTypeLookup : IItemTypeLookup
{
- ///
- /// Gets all values of the ItemFields type.
- ///
+ ///
public IReadOnlyList AllItemFields { get; } = Enum.GetValues();
- ///
- /// Gets all BaseItemKinds that are considered Programs.
- ///
+ ///
public IReadOnlyList ProgramTypes { get; } =
[
BaseItemKind.Program,
@@ -35,9 +30,7 @@ public class ItemTypeLookup : IItemTypeLookup
BaseItemKind.LiveTvChannel
];
- ///
- /// Gets all BaseItemKinds that should be excluded from parent lookup.
- ///
+ ///
public IReadOnlyList ProgramExcludeParentTypes { get; } =
[
BaseItemKind.Series,
@@ -47,27 +40,21 @@ public class ItemTypeLookup : IItemTypeLookup
BaseItemKind.PhotoAlbum
];
- ///
- /// Gets all BaseItemKinds that are considered to be provided by services.
- ///
+ ///
public IReadOnlyList ServiceTypes { get; } =
[
BaseItemKind.TvChannel,
BaseItemKind.LiveTvChannel
];
- ///
- /// Gets all BaseItemKinds that have a StartDate.
- ///
+ ///
public IReadOnlyList StartDateTypes { get; } =
[
BaseItemKind.Program,
BaseItemKind.LiveTvProgram
];
- ///
- /// Gets all BaseItemKinds that are considered Series.
- ///
+ ///
public IReadOnlyList SeriesTypes { get; } =
[
BaseItemKind.Book,
@@ -76,9 +63,7 @@ public class ItemTypeLookup : IItemTypeLookup
BaseItemKind.Season
];
- ///
- /// Gets all BaseItemKinds that are not to be evaluated for Artists.
- ///
+ ///
public IReadOnlyList ArtistExcludeParentTypes { get; } =
[
BaseItemKind.Series,
@@ -86,9 +71,7 @@ public class ItemTypeLookup : IItemTypeLookup
BaseItemKind.PhotoAlbum
];
- ///
- /// Gets all BaseItemKinds that are considered Artists.
- ///
+ ///
public IReadOnlyList ArtistsTypes { get; } =
[
BaseItemKind.Audio,
@@ -97,9 +80,7 @@ public class ItemTypeLookup : IItemTypeLookup
BaseItemKind.AudioBook
];
- ///
- /// Gets mapping for all BaseItemKinds and their expected serialisaition target.
- ///
+ ///
public IDictionary BaseItemKindNames { get; } = new Dictionary()
{
{ BaseItemKind.AggregateFolder, typeof(AggregateFolder).FullName },
diff --git a/Jellyfin.Data/Entities/BaseItemEntity.cs b/Jellyfin.Data/Entities/BaseItemEntity.cs
index dbe5a53724..cd1991891f 100644
--- a/Jellyfin.Data/Entities/BaseItemEntity.cs
+++ b/Jellyfin.Data/Entities/BaseItemEntity.cs
@@ -10,9 +10,7 @@ namespace Jellyfin.Data.Entities;
public class BaseItemEntity
{
- [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
-
- public Guid Id { get; set; }
+ public required Guid Id { get; set; }
public required string Type { get; set; }
@@ -78,12 +76,8 @@ public class BaseItemEntity
public bool IsInMixedFolder { get; set; }
- public string? LockedFields { get; set; }
-
public string? Studios { get; set; }
- public string? Audio { get; set; }
-
public string? ExternalServiceId { get; set; }
public string? Tags { get; set; }
@@ -94,8 +88,6 @@ public class BaseItemEntity
public string? UnratedType { get; set; }
- public string? TrailerTypes { get; set; }
-
public float? CriticRating { get; set; }
public string? CleanName { get; set; }
@@ -126,15 +118,13 @@ public class BaseItemEntity
public string? Tagline { get; set; }
- public string? Images { get; set; }
-
public string? ProductionLocations { get; set; }
public string? ExtraIds { get; set; }
public int? TotalBitrate { get; set; }
- public string? ExtraType { get; set; }
+ public BaseItemExtraType? ExtraType { get; set; }
public string? Artists { get; set; }
@@ -154,6 +144,8 @@ public class BaseItemEntity
public long? Size { get; set; }
+ public ProgramAudioEntity? Audio { get; set; }
+
public Guid? ParentId { get; set; }
public Guid? TopParentId { get; set; }
@@ -176,6 +168,12 @@ public class BaseItemEntity
public ICollection? AncestorIds { get; set; }
+ public ICollection? LockedFields { get; set; }
+
+ public ICollection? TrailerTypes { get; set; }
+
+ public ICollection? Images { get; set; }
+
// those are references to __LOCAL__ ids not DB ids ... TODO: Bring the whole folder structure into the DB
// public ICollection? SeriesEpisodes { get; set; }
// public BaseItemEntity? Series { get; set; }
diff --git a/Jellyfin.Data/Entities/BaseItemExtraType.cs b/Jellyfin.Data/Entities/BaseItemExtraType.cs
new file mode 100644
index 0000000000..3416974361
--- /dev/null
+++ b/Jellyfin.Data/Entities/BaseItemExtraType.cs
@@ -0,0 +1,18 @@
+namespace Jellyfin.Data.Entities;
+
+#pragma warning disable CS1591
+public enum BaseItemExtraType
+{
+ Unknown = 0,
+ Clip = 1,
+ Trailer = 2,
+ BehindTheScenes = 3,
+ DeletedScene = 4,
+ Interview = 5,
+ Scene = 6,
+ Sample = 7,
+ ThemeSong = 8,
+ ThemeVideo = 9,
+ Featurette = 10,
+ Short = 11
+}
diff --git a/Jellyfin.Data/Entities/BaseItemImageInfo.cs b/Jellyfin.Data/Entities/BaseItemImageInfo.cs
new file mode 100644
index 0000000000..6390cac58e
--- /dev/null
+++ b/Jellyfin.Data/Entities/BaseItemImageInfo.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+
+namespace Jellyfin.Data.Entities;
+#pragma warning disable CA2227
+
+///
+/// Enum TrailerTypes.
+///
+public class BaseItemImageInfo
+{
+ ///
+ /// Gets or Sets.
+ ///
+ public required Guid Id { get; set; }
+
+ ///
+ /// Gets or Sets the path to the original image.
+ ///
+ public required string Path { get; set; }
+
+ ///
+ /// Gets or Sets the time the image was last modified.
+ ///
+ public DateTime DateModified { get; set; }
+
+ ///
+ /// Gets or Sets the imagetype.
+ ///
+ public ImageInfoImageType ImageType { get; set; }
+
+ ///
+ /// Gets or Sets the width of the original image.
+ ///
+ public int Width { get; set; }
+
+ ///
+ /// Gets or Sets the height of the original image.
+ ///
+ public int Height { get; set; }
+
+#pragma warning disable CA1819
+ ///
+ /// Gets or Sets the blurhash.
+ ///
+ public byte[]? Blurhash { get; set; }
+
+ ///
+ /// Gets or Sets the reference id to the BaseItem.
+ ///
+ public required Guid ItemId { get; set; }
+
+ ///
+ /// Gets or Sets the referenced Item.
+ ///
+ public required BaseItemEntity Item { get; set; }
+}
diff --git a/Jellyfin.Data/Entities/BaseItemMetadataField.cs b/Jellyfin.Data/Entities/BaseItemMetadataField.cs
new file mode 100644
index 0000000000..2f8e910f2a
--- /dev/null
+++ b/Jellyfin.Data/Entities/BaseItemMetadataField.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+
+namespace Jellyfin.Data.Entities;
+#pragma warning disable CA2227
+
+///
+/// Enum MetadataFields.
+///
+public class BaseItemMetadataField
+{
+ ///
+ /// Gets or Sets Numerical ID of this enumeratable.
+ ///
+ public required int Id { get; set; }
+
+ ///
+ /// Gets or Sets all referenced .
+ ///
+ public required Guid ItemId { get; set; }
+
+ ///
+ /// Gets or Sets all referenced .
+ ///
+ public required BaseItemEntity Item { get; set; }
+}
diff --git a/Jellyfin.Data/Entities/BaseItemTrailerType.cs b/Jellyfin.Data/Entities/BaseItemTrailerType.cs
new file mode 100644
index 0000000000..7dee20c872
--- /dev/null
+++ b/Jellyfin.Data/Entities/BaseItemTrailerType.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+
+namespace Jellyfin.Data.Entities;
+#pragma warning disable CA2227
+///
+/// Enum TrailerTypes.
+///
+public class BaseItemTrailerType
+{
+ ///
+ /// Gets or Sets Numerical ID of this enumeratable.
+ ///
+ public required int Id { get; set; }
+
+ ///
+ /// Gets or Sets all referenced .
+ ///
+ public required Guid ItemId { get; set; }
+
+ ///
+ /// Gets or Sets all referenced .
+ ///
+ public required BaseItemEntity Item { get; set; }
+}
diff --git a/Jellyfin.Data/Entities/EnumLikeTable.cs b/Jellyfin.Data/Entities/EnumLikeTable.cs
new file mode 100644
index 0000000000..11e1d0aa92
--- /dev/null
+++ b/Jellyfin.Data/Entities/EnumLikeTable.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+
+namespace Jellyfin.Data.Entities;
+
+///
+/// Defines an Entity that is modeled after an Enum.
+///
+public abstract class EnumLikeTable
+{
+ ///
+ /// Gets or Sets Numerical ID of this enumeratable.
+ ///
+ public required int Id { get; set; }
+}
diff --git a/Jellyfin.Data/Entities/ImageInfoImageType.cs b/Jellyfin.Data/Entities/ImageInfoImageType.cs
new file mode 100644
index 0000000000..f78178dd22
--- /dev/null
+++ b/Jellyfin.Data/Entities/ImageInfoImageType.cs
@@ -0,0 +1,76 @@
+namespace Jellyfin.Data.Entities;
+
+///
+/// Enum ImageType.
+///
+public enum ImageInfoImageType
+{
+ ///
+ /// The primary.
+ ///
+ Primary = 0,
+
+ ///
+ /// The art.
+ ///
+ Art = 1,
+
+ ///
+ /// The backdrop.
+ ///
+ Backdrop = 2,
+
+ ///
+ /// The banner.
+ ///
+ Banner = 3,
+
+ ///
+ /// The logo.
+ ///
+ Logo = 4,
+
+ ///
+ /// The thumb.
+ ///
+ Thumb = 5,
+
+ ///
+ /// The disc.
+ ///
+ Disc = 6,
+
+ ///
+ /// The box.
+ ///
+ Box = 7,
+
+ ///
+ /// The screenshot.
+ ///
+ ///
+ /// This enum value is obsolete.
+ /// XmlSerializer does not serialize/deserialize objects that are marked as [Obsolete].
+ ///
+ Screenshot = 8,
+
+ ///
+ /// The menu.
+ ///
+ Menu = 9,
+
+ ///
+ /// The chapter image.
+ ///
+ Chapter = 10,
+
+ ///
+ /// The box rear.
+ ///
+ BoxRear = 11,
+
+ ///
+ /// The user profile image.
+ ///
+ Profile = 12
+}
diff --git a/Jellyfin.Data/Entities/ProgramAudioEntity.cs b/Jellyfin.Data/Entities/ProgramAudioEntity.cs
new file mode 100644
index 0000000000..fafccb13ca
--- /dev/null
+++ b/Jellyfin.Data/Entities/ProgramAudioEntity.cs
@@ -0,0 +1,37 @@
+namespace Jellyfin.Data.Entities;
+
+///
+/// Lists types of Audio.
+///
+public enum ProgramAudioEntity
+{
+ ///
+ /// Mono.
+ ///
+ Mono,
+
+ ///
+ /// Sterio.
+ ///
+ Stereo,
+
+ ///
+ /// Dolby.
+ ///
+ Dolby,
+
+ ///
+ /// DolbyDigital.
+ ///
+ DolbyDigital,
+
+ ///
+ /// Thx.
+ ///
+ Thx,
+
+ ///
+ /// Atmos.
+ ///
+ Atmos
+}
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
index 480d83eb1c..6ddab9e3db 100644
--- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
+++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
+using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller;
@@ -69,6 +70,8 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr
context.AncestorIds.Where(e => e.ItemId == id).ExecuteDelete();
context.ItemValues.Where(e => e.ItemId == id).ExecuteDelete();
context.BaseItems.Where(e => e.Id == id).ExecuteDelete();
+ context.BaseItemImageInfos.Where(e => e.ItemId == id).ExecuteDelete();
+ context.BaseItemProviders.Where(e => e.ItemId == id).ExecuteDelete();
context.SaveChanges();
transaction.Commit();
}
@@ -229,7 +232,12 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr
var result = new QueryResult();
using var context = dbProvider.CreateDbContext();
- var dbQuery = TranslateQuery(context.BaseItems, context, filter)
+ IQueryable dbQuery = context.BaseItems
+ .Include(e => e.ExtraType)
+ .Include(e => e.TrailerTypes)
+ .Include(e => e.Images)
+ .Include(e => e.LockedFields);
+ dbQuery = TranslateQuery(dbQuery, context, filter)
.DistinctBy(e => e.Id);
if (filter.EnableTotalRecordCount)
{
@@ -585,8 +593,8 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr
if (filter.TrailerTypes.Length > 0)
{
- var trailerTypes = filter.TrailerTypes.Select(e => e.ToString()).ToArray();
- baseQuery = baseQuery.Where(e => trailerTypes.Any(f => e.TrailerTypes!.Contains(f)));
+ var trailerTypes = filter.TrailerTypes.Select(e => (int)e).ToArray();
+ baseQuery = baseQuery.Where(e => trailerTypes.Any(f => e.TrailerTypes!.Any(w => w.Id == f)));
}
if (filter.IsAiring.HasValue)
@@ -666,8 +674,8 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr
if (filter.ImageTypes.Length > 0)
{
- var imgTypes = filter.ImageTypes.Select(e => e.ToString()).ToArray();
- baseQuery = baseQuery.Where(e => imgTypes.Any(f => e.Images!.Contains(f)));
+ var imgTypes = filter.ImageTypes.Select(e => (ImageInfoImageType)e).ToArray();
+ baseQuery = baseQuery.Where(e => imgTypes.Any(f => e.Images!.Any(w => w.ImageType == f)));
}
if (filter.IsLiked.HasValue)
@@ -1206,12 +1214,12 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr
{
ArgumentNullException.ThrowIfNull(item);
- var images = SerializeImages(item.ImageInfos);
+ var images = item.ImageInfos.Select(e => Map(item.Id, e));
using var db = dbProvider.CreateDbContext();
-
- db.BaseItems
- .Where(e => e.Id == item.Id)
- .ExecuteUpdate(e => e.SetProperty(f => f.Images, images));
+ using var transaction = db.Database.BeginTransaction();
+ db.BaseItemImageInfos.Where(e => e.ItemId == item.Id).ExecuteDelete();
+ db.BaseItemImageInfos.AddRange(images);
+ transaction.Commit();
}
///
@@ -1260,29 +1268,32 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr
context.AncestorIds.Where(e => e.ItemId == entity.Id).ExecuteDelete();
if (item.Item.SupportsAncestors && item.AncestorIds != null)
{
+ entity.AncestorIds = new List();
foreach (var ancestorId in item.AncestorIds)
{
- context.AncestorIds.Add(new Data.Entities.AncestorId()
+ entity.AncestorIds.Add(new AncestorId()
{
Item = entity,
AncestorIdText = ancestorId.ToString(),
Id = ancestorId,
- ItemId = Guid.Empty
+ ItemId = entity.Id
});
}
}
var itemValues = GetItemValuesToSave(item.Item, item.InheritedTags);
context.ItemValues.Where(e => e.ItemId == entity.Id).ExecuteDelete();
+ entity.ItemValues = new List();
+
foreach (var itemValue in itemValues)
{
- context.ItemValues.Add(new()
+ entity.ItemValues.Add(new()
{
Item = entity,
Type = itemValue.MagicNumber,
Value = itemValue.Value,
CleanValue = GetCleanValue(itemValue.Value),
- ItemId = Guid.Empty
+ ItemId = entity.Id
});
}
}
@@ -1366,26 +1377,17 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr
if (entity.ExtraType is not null)
{
- dto.ExtraType = Enum.Parse(entity.ExtraType);
+ dto.ExtraType = (ExtraType)entity.ExtraType;
}
if (entity.LockedFields is not null)
{
- List? fields = null;
- foreach (var i in entity.LockedFields.AsSpan().Split('|'))
- {
- if (Enum.TryParse(i, true, out MetadataField parsedValue))
- {
- (fields ??= new List()).Add(parsedValue);
- }
- }
-
- dto.LockedFields = fields?.ToArray() ?? Array.Empty();
+ dto.LockedFields = entity.LockedFields?.Select(e => (MetadataField)e.Id).ToArray() ?? [];
}
if (entity.Audio is not null)
{
- dto.Audio = Enum.Parse(entity.Audio);
+ dto.Audio = (ProgramAudio)entity.Audio;
}
dto.ExtraIds = string.IsNullOrWhiteSpace(entity.ExtraIds) ? null : entity.ExtraIds.Split('|').Select(e => Guid.Parse(e)).ToArray();
@@ -1408,16 +1410,7 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr
if (dto is Trailer trailer)
{
- List? types = null;
- foreach (var i in entity.TrailerTypes.AsSpan().Split('|'))
- {
- if (Enum.TryParse(i, true, out TrailerType parsedValue))
- {
- (types ??= new List()).Add(parsedValue);
- }
- }
-
- trailer.TrailerTypes = types?.ToArray() ?? Array.Empty();
+ trailer.TrailerTypes = entity.TrailerTypes?.Select(e => (TrailerType)e.Id).ToArray() ?? [];
}
if (dto is Video video)
@@ -1455,7 +1448,7 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr
if (entity.Images is not null)
{
- dto.ImageInfos = DeserializeImages(entity.Images);
+ dto.ImageInfos = entity.Images.Select(Map).ToArray();
}
// dto.Type = entity.Type;
@@ -1490,8 +1483,8 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr
var entity = new BaseItemEntity()
{
Type = dto.GetType().ToString(),
+ Id = dto.Id
};
- entity.Id = dto.Id;
entity.ParentId = dto.ParentId;
entity.Path = GetPathToSave(dto.Path);
entity.EndDate = dto.EndDate.GetValueOrDefault();
@@ -1533,21 +1526,35 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr
entity.OwnerId = dto.OwnerId.ToString();
entity.Width = dto.Width;
entity.Height = dto.Height;
- entity.Provider = dto.ProviderIds.Select(e => new Data.Entities.BaseItemProvider()
+ entity.Provider = dto.ProviderIds.Select(e => new BaseItemProvider()
{
Item = entity,
ProviderId = e.Key,
ProviderValue = e.Value
}).ToList();
- entity.Audio = dto.Audio?.ToString();
- entity.ExtraType = dto.ExtraType?.ToString();
+ if (dto.Audio.HasValue)
+ {
+ entity.Audio = (ProgramAudioEntity)dto.Audio;
+ }
+
+ if (dto.ExtraType.HasValue)
+ {
+ entity.ExtraType = (BaseItemExtraType)dto.ExtraType;
+ }
entity.ExtraIds = dto.ExtraIds is not null ? string.Join('|', dto.ExtraIds) : null;
entity.ProductionLocations = dto.ProductionLocations is not null ? string.Join('|', dto.ProductionLocations) : null;
entity.Studios = dto.Studios is not null ? string.Join('|', dto.Studios) : null;
entity.Tags = dto.Tags is not null ? string.Join('|', dto.Tags) : null;
- entity.LockedFields = dto.LockedFields is not null ? string.Join('|', dto.LockedFields) : null;
+ entity.LockedFields = dto.LockedFields is not null ? dto.LockedFields
+ .Select(e => new BaseItemMetadataField()
+ {
+ Id = (int)e,
+ Item = entity,
+ ItemId = entity.Id
+ })
+ .ToArray() : null;
if (dto is IHasProgramAttributes hasProgramAttributes)
{
@@ -1562,11 +1569,6 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr
entity.ExternalServiceId = liveTvChannel.ServiceName;
}
- if (dto is Trailer trailer)
- {
- entity.LockedFields = trailer.LockedFields is not null ? string.Join('|', trailer.LockedFields) : null;
- }
-
if (dto is Video video)
{
entity.PrimaryVersionId = video.PrimaryVersionId;
@@ -1602,7 +1604,17 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr
if (dto.ImageInfos is not null)
{
- entity.Images = SerializeImages(dto.ImageInfos);
+ entity.Images = dto.ImageInfos.Select(f => Map(dto.Id, f)).ToArray();
+ }
+
+ if (dto is Trailer trailer)
+ {
+ entity.TrailerTypes = trailer.TrailerTypes?.Select(e => new BaseItemTrailerType()
+ {
+ Id = (int)e,
+ Item = entity,
+ ItemId = entity.Id
+ }).ToArray() ?? [];
}
// dto.Type = entity.Type;
@@ -1863,90 +1875,33 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr
}
}
- internal string? SerializeImages(ItemImageInfo[] images)
+ private static BaseItemImageInfo Map(Guid baseItemId, ItemImageInfo e)
{
- if (images.Length == 0)
+ return new BaseItemImageInfo()
{
- return null;
- }
-
- StringBuilder str = new StringBuilder();
- foreach (var i in images)
- {
- if (string.IsNullOrWhiteSpace(i.Path))
- {
- continue;
- }
-
- AppendItemImageInfo(str, i);
- str.Append('|');
- }
-
- str.Length -= 1; // Remove last |
- return str.ToString();
+ ItemId = baseItemId,
+ Id = Guid.NewGuid(),
+ Path = e.Path,
+ Blurhash = e.BlurHash != null ? Encoding.UTF8.GetBytes(e.BlurHash) : null,
+ DateModified = e.DateModified,
+ Height = e.Height,
+ Width = e.Width,
+ ImageType = (ImageInfoImageType)e.Type,
+ Item = null!
+ };
}
- internal ItemImageInfo[] DeserializeImages(string value)
+ private static ItemImageInfo Map(BaseItemImageInfo e)
{
- if (string.IsNullOrWhiteSpace(value))
+ return new ItemImageInfo()
{
- return Array.Empty();
- }
-
- // TODO The following is an ugly performance optimization, but it's extremely unlikely that the data in the database would be malformed
- var valueSpan = value.AsSpan();
- var count = valueSpan.Count('|') + 1;
-
- var position = 0;
- var result = new ItemImageInfo[count];
- foreach (var part in valueSpan.Split('|'))
- {
- var image = ItemImageInfoFromValueString(part);
-
- if (image is not null)
- {
- result[position++] = image;
- }
- }
-
- if (position == count)
- {
- return result;
- }
-
- if (position == 0)
- {
- return Array.Empty();
- }
-
- // Extremely unlikely, but somehow one or more of the image strings were malformed. Cut the array.
- return result[..position];
- }
-
- private void AppendItemImageInfo(StringBuilder bldr, ItemImageInfo image)
- {
- const char Delimiter = '*';
-
- var path = image.Path ?? string.Empty;
-
- bldr.Append(GetPathToSave(path))
- .Append(Delimiter)
- .Append(image.DateModified.Ticks)
- .Append(Delimiter)
- .Append(image.Type)
- .Append(Delimiter)
- .Append(image.Width)
- .Append(Delimiter)
- .Append(image.Height);
-
- var hash = image.BlurHash;
- if (!string.IsNullOrEmpty(hash))
- {
- bldr.Append(Delimiter)
- // Replace delimiters with other characters.
- // This can be removed when we migrate to a proper DB.
- .Append(hash.Replace(Delimiter, '/').Replace('|', '\\'));
- }
+ Path = e.Path,
+ BlurHash = e.Blurhash != null ? Encoding.UTF8.GetString(e.Blurhash) : null,
+ DateModified = e.DateModified,
+ Height = e.Height,
+ Width = e.Width,
+ Type = (ImageType)e.ImageType
+ };
}
private string? GetPathToSave(string path)
@@ -1964,111 +1919,6 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr
return appHost.ExpandVirtualPath(path);
}
- internal ItemImageInfo? ItemImageInfoFromValueString(ReadOnlySpan value)
- {
- const char Delimiter = '*';
-
- var nextSegment = value.IndexOf(Delimiter);
- if (nextSegment == -1)
- {
- return null;
- }
-
- ReadOnlySpan path = value[..nextSegment];
- value = value[(nextSegment + 1)..];
- nextSegment = value.IndexOf(Delimiter);
- if (nextSegment == -1)
- {
- return null;
- }
-
- ReadOnlySpan dateModified = value[..nextSegment];
- value = value[(nextSegment + 1)..];
- nextSegment = value.IndexOf(Delimiter);
- if (nextSegment == -1)
- {
- nextSegment = value.Length;
- }
-
- ReadOnlySpan imageType = value[..nextSegment];
-
- var image = new ItemImageInfo
- {
- Path = RestorePath(path.ToString())
- };
-
- if (long.TryParse(dateModified, CultureInfo.InvariantCulture, out var ticks)
- && ticks >= DateTime.MinValue.Ticks
- && ticks <= DateTime.MaxValue.Ticks)
- {
- image.DateModified = new DateTime(ticks, DateTimeKind.Utc);
- }
- else
- {
- return null;
- }
-
- if (Enum.TryParse(imageType, true, out ImageType type))
- {
- image.Type = type;
- }
- else
- {
- return null;
- }
-
- // Optional parameters: width*height*blurhash
- if (nextSegment + 1 < value.Length - 1)
- {
- value = value[(nextSegment + 1)..];
- nextSegment = value.IndexOf(Delimiter);
- if (nextSegment == -1 || nextSegment == value.Length)
- {
- return image;
- }
-
- ReadOnlySpan widthSpan = value[..nextSegment];
-
- value = value[(nextSegment + 1)..];
- nextSegment = value.IndexOf(Delimiter);
- if (nextSegment == -1)
- {
- nextSegment = value.Length;
- }
-
- ReadOnlySpan heightSpan = value[..nextSegment];
-
- if (int.TryParse(widthSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var width)
- && int.TryParse(heightSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var height))
- {
- image.Width = width;
- image.Height = height;
- }
-
- if (nextSegment < value.Length - 1)
- {
- value = value[(nextSegment + 1)..];
- var length = value.Length;
-
- Span blurHashSpan = stackalloc char[length];
- for (int i = 0; i < length; i++)
- {
- var c = value[i];
- blurHashSpan[i] = c switch
- {
- '/' => Delimiter,
- '\\' => '|',
- _ => c
- };
- }
-
- image.BlurHash = new string(blurHashSpan);
- }
- }
-
- return image;
- }
-
private List GetItemByNameTypesInQuery(InternalItemsQuery query)
{
var list = new List();
diff --git a/Jellyfin.Server.Implementations/JellyfinDbContext.cs b/Jellyfin.Server.Implementations/JellyfinDbContext.cs
index a9eda1b64a..406230a70a 100644
--- a/Jellyfin.Server.Implementations/JellyfinDbContext.cs
+++ b/Jellyfin.Server.Implementations/JellyfinDbContext.cs
@@ -131,6 +131,21 @@ public class JellyfinDbContext(DbContextOptions options, ILog
///
public DbSet BaseItemProviders => Set();
+ ///
+ /// Gets the .
+ ///
+ public DbSet BaseItemImageInfos => Set();
+
+ ///
+ /// Gets the .
+ ///
+ public DbSet BaseItemMetadataFields => Set();
+
+ ///
+ /// Gets the .
+ ///
+ public DbSet BaseItemTrailerTypes => Set();
+
/*public DbSet Artwork => Set();
public DbSet Books => Set();
diff --git a/Jellyfin.Server.Implementations/Migrations/20241009225800_ExpandedBaseItemFields.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20241009225800_ExpandedBaseItemFields.Designer.cs
new file mode 100644
index 0000000000..7f69e84487
--- /dev/null
+++ b/Jellyfin.Server.Implementations/Migrations/20241009225800_ExpandedBaseItemFields.Designer.cs
@@ -0,0 +1,1540 @@
+//
+using System;
+using Jellyfin.Server.Implementations;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Jellyfin.Server.Implementations.Migrations
+{
+ [DbContext(typeof(JellyfinDbContext))]
+ [Migration("20241009225800_ExpandedBaseItemFields")]
+ partial class ExpandedBaseItemFields
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "8.0.10");
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("DayOfWeek")
+ .HasColumnType("INTEGER");
+
+ b.Property("EndHour")
+ .HasColumnType("REAL");
+
+ b.Property("StartHour")
+ .HasColumnType("REAL");
+
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AccessSchedules");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("DateCreated")
+ .HasColumnType("TEXT");
+
+ b.Property("ItemId")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("LogSeverity")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("Overview")
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("ShortOverview")
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DateCreated");
+
+ b.ToTable("ActivityLogs");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.AncestorId", b =>
+ {
+ b.Property("ItemId")
+ .HasColumnType("TEXT");
+
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("AncestorIdText")
+ .HasColumnType("TEXT");
+
+ b.HasKey("ItemId", "Id");
+
+ b.HasIndex("Id");
+
+ b.HasIndex("ItemId", "AncestorIdText");
+
+ b.ToTable("AncestorIds");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.AttachmentStreamInfo", b =>
+ {
+ b.Property("ItemId")
+ .HasColumnType("TEXT");
+
+ b.Property("Index")
+ .HasColumnType("INTEGER");
+
+ b.Property("Codec")
+ .IsRequired()
+ .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.Data.Entities.BaseItemEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Album")
+ .HasColumnType("TEXT");
+
+ b.Property("AlbumArtists")
+ .HasColumnType("TEXT");
+
+ b.Property("Artists")
+ .HasColumnType("TEXT");
+
+ b.Property("Audio")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChannelId")
+ .HasColumnType("TEXT");
+
+ 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("TEXT");
+
+ b.Property("DateLastMediaAdded")
+ .HasColumnType("TEXT");
+
+ b.Property("DateLastRefreshed")
+ .HasColumnType("TEXT");
+
+ b.Property("DateLastSaved")
+ .HasColumnType("TEXT");
+
+ b.Property("DateModified")
+ .HasColumnType("TEXT");
+
+ b.Property("EndDate")
+ .HasColumnType("TEXT");
+
+ b.Property("EpisodeTitle")
+ .HasColumnType("TEXT");
+
+ b.Property("ExternalId")
+ .HasColumnType("TEXT");
+
+ b.Property("ExternalSeriesId")
+ .HasColumnType("TEXT");
+
+ b.Property("ExternalServiceId")
+ .HasColumnType("TEXT");
+
+ b.Property("ExtraIds")
+ .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("InheritedParentalRatingValue")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsFolder")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsInMixedFolder")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsMovie")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsRepeat")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsSeries")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsVirtualItem")
+ .HasColumnType("INTEGER");
+
+ 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("OriginalTitle")
+ .HasColumnType("TEXT");
+
+ b.Property("Overview")
+ .HasColumnType("TEXT");
+
+ b.Property("OwnerId")
+ .HasColumnType("TEXT");
+
+ b.Property("ParentId")
+ .HasColumnType("TEXT");
+
+ b.Property("ParentIndexNumber")
+ .HasColumnType("INTEGER");
+
+ b.Property("Path")
+ .HasColumnType("TEXT");
+
+ b.Property("PreferredMetadataCountryCode")
+ .HasColumnType("TEXT");
+
+ b.Property("PreferredMetadataLanguage")
+ .HasColumnType("TEXT");
+
+ b.Property("PremiereDate")
+ .HasColumnType("TEXT");
+
+ b.Property("PresentationUniqueKey")
+ .HasColumnType("TEXT");
+
+ b.Property("PrimaryVersionId")
+ .HasColumnType("TEXT");
+
+ b.Property("ProductionLocations")
+ .HasColumnType("TEXT");
+
+ b.Property("ProductionYear")
+ .HasColumnType("INTEGER");
+
+ b.Property("RunTimeTicks")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeasonId")
+ .HasColumnType("TEXT");
+
+ b.Property("SeasonName")
+ .HasColumnType("TEXT");
+
+ b.Property("SeriesId")
+ .HasColumnType("TEXT");
+
+ b.Property("SeriesName")
+ .HasColumnType("TEXT");
+
+ b.Property("SeriesPresentationUniqueKey")
+ .HasColumnType("TEXT");
+
+ b.Property("ShowId")
+ .HasColumnType("TEXT");
+
+ b.Property("Size")
+ .HasColumnType("INTEGER");
+
+ b.Property("SortName")
+ .HasColumnType("TEXT");
+
+ b.Property("StartDate")
+ .HasColumnType("TEXT");
+
+ b.Property("Studios")
+ .HasColumnType("TEXT");
+
+ b.Property("Tagline")
+ .HasColumnType("TEXT");
+
+ b.Property("Tags")
+ .HasColumnType("TEXT");
+
+ b.Property("TopParentId")
+ .HasColumnType("TEXT");
+
+ b.Property("TotalBitrate")
+ .HasColumnType("INTEGER");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("UnratedType")
+ .HasColumnType("TEXT");
+
+ b.Property("UserDataKey")
+ .HasColumnType("TEXT");
+
+ b.Property("Width")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ParentId");
+
+ b.HasIndex("Path");
+
+ b.HasIndex("PresentationUniqueKey");
+
+ b.HasIndex("TopParentId", "Id");
+
+ b.HasIndex("UserDataKey", "Type");
+
+ b.HasIndex("Type", "TopParentId", "Id");
+
+ b.HasIndex("Type", "TopParentId", "PresentationUniqueKey");
+
+ b.HasIndex("Type", "TopParentId", "StartDate");
+
+ b.HasIndex("Id", "Type", "IsFolder", "IsVirtualItem");
+
+ b.HasIndex("MediaType", "TopParentId", "IsVirtualItem", "PresentationUniqueKey");
+
+ b.HasIndex("Type", "SeriesPresentationUniqueKey", "IsFolder", "IsVirtualItem");
+
+ b.HasIndex("Type", "SeriesPresentationUniqueKey", "PresentationUniqueKey", "SortName");
+
+ b.HasIndex("IsFolder", "TopParentId", "IsVirtualItem", "PresentationUniqueKey", "DateCreated");
+
+ b.HasIndex("Type", "TopParentId", "IsVirtualItem", "PresentationUniqueKey", "DateCreated");
+
+ b.ToTable("BaseItems");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemImageInfo", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Blurhash")
+ .HasColumnType("BLOB");
+
+ b.Property("DateModified")
+ .HasColumnType("TEXT");
+
+ b.Property("Height")
+ .HasColumnType("INTEGER");
+
+ b.Property("ImageType")
+ .HasColumnType("INTEGER");
+
+ b.Property("ItemId")
+ .HasColumnType("TEXT");
+
+ b.Property("Path")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Width")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ItemId");
+
+ b.ToTable("BaseItemImageInfos");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemMetadataField", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("INTEGER");
+
+ b.Property("ItemId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id", "ItemId");
+
+ b.HasIndex("ItemId");
+
+ b.ToTable("BaseItemMetadataFields");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemProvider", b =>
+ {
+ b.Property("ItemId")
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderId")
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderValue")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("ItemId", "ProviderId");
+
+ b.HasIndex("ProviderId", "ProviderValue", "ItemId");
+
+ b.ToTable("BaseItemProviders");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemTrailerType", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("INTEGER");
+
+ b.Property("ItemId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id", "ItemId");
+
+ b.HasIndex("ItemId");
+
+ b.ToTable("BaseItemTrailerTypes");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.Chapter", b =>
+ {
+ b.Property("ItemId")
+ .HasColumnType("TEXT");
+
+ b.Property("ChapterIndex")
+ .HasColumnType("INTEGER");
+
+ b.Property("ImageDateModified")
+ .HasColumnType("TEXT");
+
+ b.Property("ImagePath")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("StartPositionTicks")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("ItemId", "ChapterIndex");
+
+ b.ToTable("Chapters");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Client")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("ItemId")
+ .HasColumnType("TEXT");
+
+ b.Property("Key")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "ItemId", "Client", "Key")
+ .IsUnique();
+
+ b.ToTable("CustomItemDisplayPreferences");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ChromecastVersion")
+ .HasColumnType("INTEGER");
+
+ b.Property("Client")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("DashboardTheme")
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("EnableNextVideoInfoOverlay")
+ .HasColumnType("INTEGER");
+
+ b.Property("IndexBy")
+ .HasColumnType("INTEGER");
+
+ b.Property("ItemId")
+ .HasColumnType("TEXT");
+
+ b.Property("ScrollDirection")
+ .HasColumnType("INTEGER");
+
+ b.Property("ShowBackdrop")
+ .HasColumnType("INTEGER");
+
+ b.Property("ShowSidebar")
+ .HasColumnType("INTEGER");
+
+ b.Property("SkipBackwardLength")
+ .HasColumnType("INTEGER");
+
+ b.Property("SkipForwardLength")
+ .HasColumnType("INTEGER");
+
+ b.Property("TvHome")
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "ItemId", "Client")
+ .IsUnique();
+
+ b.ToTable("DisplayPreferences");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ 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.Data.Entities.ImageInfo", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("Path")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("ImageInfos");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Client")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("IndexBy")
+ .HasColumnType("INTEGER");
+
+ b.Property("ItemId")
+ .HasColumnType("TEXT");
+
+ b.Property("RememberIndexing")
+ .HasColumnType("INTEGER");
+
+ b.Property("RememberSorting")
+ .HasColumnType("INTEGER");
+
+ b.Property("SortBy")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.Property("SortOrder")
+ .HasColumnType("INTEGER");
+
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.Property("ViewType")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("ItemDisplayPreferences");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.ItemValue", b =>
+ {
+ b.Property("ItemId")
+ .HasColumnType("TEXT");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.Property("CleanValue")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("ItemId", "Type", "Value");
+
+ b.HasIndex("ItemId", "Type", "CleanValue");
+
+ b.ToTable("ItemValues");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.MediaSegment", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("EndTicks")
+ .HasColumnType("INTEGER");
+
+ b.Property("ItemId")
+ .HasColumnType("TEXT");
+
+ b.Property("SegmentProviderId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("StartTicks")
+ .HasColumnType("INTEGER");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("MediaSegments");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.MediaStreamInfo", b =>
+ {
+ b.Property("ItemId")
+ .HasColumnType("TEXT");
+
+ 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")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("CodecTimeBase")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("ColorPrimaries")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("ColorSpace")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("ColorTransfer")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Comment")
+ .IsRequired()
+ .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