diff --git a/CHANGELOG.md b/CHANGELOG.md
index f345979ed..1e31447ef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -56,6 +56,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- NVIDIA: fix stream failure with certain content that should decode in hardware but falls back to software
- Fix stream failure when configured fallback filler collection is empty
- Fix high CPU when errors are displayed; errors will now work ahead before throttling to realtime, similar to primary content
+- Fix startup error caused by duplicate smart collection names (and no longer allow duplicate smart collection names)
### Changed
- No longer round framerate to nearest integer when normalizing framerate
diff --git a/Directory.Build.props b/Directory.Build.props
index 649687ae8..22f540d9b 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -2,5 +2,6 @@
develop
false
+ true
-
\ No newline at end of file
+
diff --git a/ErsatzTV.Application/MediaCollections/Commands/UpdateSmartCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/UpdateSmartCollectionHandler.cs
index ba79c2dde..686b14cba 100644
--- a/ErsatzTV.Application/MediaCollections/Commands/UpdateSmartCollectionHandler.cs
+++ b/ErsatzTV.Application/MediaCollections/Commands/UpdateSmartCollectionHandler.cs
@@ -12,7 +12,9 @@ using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Application.MediaCollections;
-public class UpdateSmartCollectionHandler : IRequestHandler>
+public class
+ UpdateSmartCollectionHandler : IRequestHandler>
{
private readonly ChannelWriter _channel;
private readonly IDbContextFactory _dbContextFactory;
@@ -71,7 +73,8 @@ public class UpdateSmartCollectionHandler : IRequestHandler> Validate(
TvContext dbContext,
UpdateSmartCollection request,
- CancellationToken cancellationToken) => SmartCollectionMustExist(dbContext, request, cancellationToken);
+ CancellationToken cancellationToken) => ValidateName(dbContext, request)
+ .BindT(_ => SmartCollectionMustExist(dbContext, request, cancellationToken));
private static Task> SmartCollectionMustExist(
TvContext dbContext,
@@ -80,4 +83,23 @@ public class UpdateSmartCollectionHandler : IRequestHandler c.Id, c => c.Id == updateCollection.Id, cancellationToken)
.Map(o => o.ToValidation("SmartCollection does not exist."));
+
+ private static async Task> ValidateName(
+ TvContext dbContext,
+ UpdateSmartCollection updateCollection)
+ {
+ List allNames = await dbContext.SmartCollections
+ .Where(c => c.Id != updateCollection.Id)
+ .Map(c => c.Name)
+ .ToListAsync();
+
+ Validation result1 = updateCollection.NotEmpty(c => c.Name)
+ .Bind(_ => updateCollection.NotLongerThan(50)(c => c.Name));
+
+ var result2 = Optional(updateCollection.Name)
+ .Where(name => !allNames.Contains(name))
+ .ToValidation("SmartCollection name must be unique");
+
+ return (result1, result2).Apply((_, _) => updateCollection.Name);
+ }
}
diff --git a/ErsatzTV.Infrastructure.MySql/Migrations/20251213202531_Fix_DuplicateSmartCollectionName.Designer.cs b/ErsatzTV.Infrastructure.MySql/Migrations/20251213202531_Fix_DuplicateSmartCollectionName.Designer.cs
new file mode 100644
index 000000000..b222e0fd4
--- /dev/null
+++ b/ErsatzTV.Infrastructure.MySql/Migrations/20251213202531_Fix_DuplicateSmartCollectionName.Designer.cs
@@ -0,0 +1,6882 @@
+//
+using System;
+using ErsatzTV.Infrastructure.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace ErsatzTV.Infrastructure.MySql.Migrations
+{
+ [DbContext(typeof(TvContext))]
+ [Migration("20251213202531_Fix_DuplicateSmartCollectionName")]
+ partial class Fix_DuplicateSmartCollectionName
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.11")
+ .HasAnnotation("Relational:MaxIdentifierLength", 64);
+
+ MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("ArtistMetadataId")
+ .HasColumnType("int");
+
+ b.Property("ArtworkId")
+ .HasColumnType("int");
+
+ b.Property("EpisodeMetadataId")
+ .HasColumnType("int");
+
+ b.Property("ImageMetadataId")
+ .HasColumnType("int");
+
+ b.Property("MovieMetadataId")
+ .HasColumnType("int");
+
+ b.Property("MusicVideoMetadataId")
+ .HasColumnType("int");
+
+ b.Property("Name")
+ .HasColumnType("longtext");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.Property("OtherVideoMetadataId")
+ .HasColumnType("int");
+
+ b.Property("RemoteStreamMetadataId")
+ .HasColumnType("int");
+
+ b.Property("Role")
+ .HasColumnType("longtext");
+
+ b.Property("SeasonMetadataId")
+ .HasColumnType("int");
+
+ b.Property("ShowMetadataId")
+ .HasColumnType("int");
+
+ b.Property("SongMetadataId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ArtistMetadataId");
+
+ b.HasIndex("ArtworkId")
+ .IsUnique();
+
+ b.HasIndex("EpisodeMetadataId");
+
+ b.HasIndex("ImageMetadataId");
+
+ b.HasIndex("MovieMetadataId");
+
+ b.HasIndex("MusicVideoMetadataId");
+
+ b.HasIndex("OtherVideoMetadataId");
+
+ b.HasIndex("RemoteStreamMetadataId");
+
+ b.HasIndex("SeasonMetadataId");
+
+ b.HasIndex("ShowMetadataId");
+
+ b.HasIndex("SongMetadataId");
+
+ b.ToTable("Actor", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.ArtistMetadata", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("ArtistId")
+ .HasColumnType("int");
+
+ b.Property("Biography")
+ .HasColumnType("longtext");
+
+ b.Property("DateAdded")
+ .HasColumnType("datetime(6)");
+
+ b.Property("DateUpdated")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Disambiguation")
+ .HasColumnType("longtext");
+
+ b.Property("Formed")
+ .HasColumnType("longtext");
+
+ b.Property("MetadataKind")
+ .HasColumnType("int");
+
+ b.Property("OriginalTitle")
+ .HasColumnType("longtext");
+
+ b.Property("ReleaseDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("SortTitle")
+ .HasColumnType("longtext");
+
+ b.Property("Title")
+ .HasColumnType("longtext");
+
+ b.Property("Year")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ArtistId");
+
+ b.ToTable("ArtistMetadata", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.Artwork", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("ArtistMetadataId")
+ .HasColumnType("int");
+
+ b.Property("ArtworkKind")
+ .HasColumnType("int");
+
+ b.Property("BlurHash43")
+ .HasColumnType("longtext");
+
+ b.Property("BlurHash54")
+ .HasColumnType("longtext");
+
+ b.Property("BlurHash64")
+ .HasColumnType("longtext");
+
+ b.Property("ChannelId")
+ .HasColumnType("int");
+
+ b.Property("DateAdded")
+ .HasColumnType("datetime(6)");
+
+ b.Property("DateUpdated")
+ .HasColumnType("datetime(6)");
+
+ b.Property("EpisodeMetadataId")
+ .HasColumnType("int");
+
+ b.Property("ImageMetadataId")
+ .HasColumnType("int");
+
+ b.Property("MovieMetadataId")
+ .HasColumnType("int");
+
+ b.Property("MusicVideoMetadataId")
+ .HasColumnType("int");
+
+ b.Property("OriginalContentType")
+ .HasColumnType("longtext");
+
+ b.Property("OtherVideoMetadataId")
+ .HasColumnType("int");
+
+ b.Property("Path")
+ .HasColumnType("longtext");
+
+ b.Property("RemoteStreamMetadataId")
+ .HasColumnType("int");
+
+ b.Property("SeasonMetadataId")
+ .HasColumnType("int");
+
+ b.Property("ShowMetadataId")
+ .HasColumnType("int");
+
+ b.Property("SongMetadataId")
+ .HasColumnType("int");
+
+ b.Property("SourcePath")
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ArtistMetadataId");
+
+ b.HasIndex("ChannelId");
+
+ b.HasIndex("EpisodeMetadataId");
+
+ b.HasIndex("ImageMetadataId");
+
+ b.HasIndex("MovieMetadataId");
+
+ b.HasIndex("MusicVideoMetadataId");
+
+ b.HasIndex("OtherVideoMetadataId");
+
+ b.HasIndex("RemoteStreamMetadataId");
+
+ b.HasIndex("SeasonMetadataId");
+
+ b.HasIndex("ShowMetadataId");
+
+ b.HasIndex("SongMetadataId");
+
+ b.ToTable("Artwork", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.BlockItemGraphicsElement", b =>
+ {
+ b.Property("BlockItemId")
+ .HasColumnType("int");
+
+ b.Property("GraphicsElementId")
+ .HasColumnType("int");
+
+ b.HasKey("BlockItemId", "GraphicsElementId");
+
+ b.HasIndex("GraphicsElementId");
+
+ b.ToTable("BlockItemGraphicsElement");
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.BlockItemWatermark", b =>
+ {
+ b.Property("BlockItemId")
+ .HasColumnType("int");
+
+ b.Property("WatermarkId")
+ .HasColumnType("int");
+
+ b.HasKey("BlockItemId", "WatermarkId");
+
+ b.HasIndex("WatermarkId");
+
+ b.ToTable("BlockItemWatermark");
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("Categories")
+ .HasColumnType("longtext");
+
+ b.Property("FFmpegProfileId")
+ .HasColumnType("int");
+
+ b.Property("FallbackFillerId")
+ .HasColumnType("int");
+
+ b.Property("Group")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("longtext")
+ .HasDefaultValue("ErsatzTV");
+
+ b.Property("IdleBehavior")
+ .HasColumnType("int");
+
+ b.Property("IsEnabled")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("tinyint(1)")
+ .HasDefaultValue(true);
+
+ b.Property("MirrorSourceChannelId")
+ .HasColumnType("int");
+
+ b.Property("MusicVideoCreditsMode")
+ .HasColumnType("int");
+
+ b.Property("MusicVideoCreditsTemplate")
+ .HasColumnType("longtext");
+
+ b.Property("Name")
+ .HasColumnType("longtext");
+
+ b.Property("Number")
+ .HasColumnType("varchar(255)");
+
+ b.Property("PlayoutMode")
+ .HasColumnType("int");
+
+ b.Property("PlayoutOffset")
+ .HasColumnType("time(6)");
+
+ b.Property("PlayoutSource")
+ .HasColumnType("int");
+
+ b.Property("PreferredAudioLanguageCode")
+ .HasColumnType("longtext");
+
+ b.Property("PreferredAudioTitle")
+ .HasColumnType("longtext");
+
+ b.Property("PreferredSubtitleLanguageCode")
+ .HasColumnType("longtext");
+
+ b.Property("ShowInEpg")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("tinyint(1)")
+ .HasDefaultValue(true);
+
+ b.Property("SongVideoMode")
+ .HasColumnType("int");
+
+ b.Property("SortNumber")
+ .HasColumnType("double");
+
+ b.Property("StreamSelector")
+ .HasColumnType("longtext");
+
+ b.Property("StreamSelectorMode")
+ .HasColumnType("int");
+
+ b.Property("StreamingMode")
+ .HasColumnType("int");
+
+ b.Property("SubtitleMode")
+ .HasColumnType("int");
+
+ b.Property("TranscodeMode")
+ .HasColumnType("int");
+
+ b.Property("UniqueId")
+ .HasColumnType("char(36)");
+
+ b.Property("WatermarkId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("FFmpegProfileId");
+
+ b.HasIndex("FallbackFillerId");
+
+ b.HasIndex("MirrorSourceChannelId");
+
+ b.HasIndex("Number")
+ .IsUnique();
+
+ b.HasIndex("WatermarkId");
+
+ b.ToTable("Channel", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.ChannelWatermark", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("DurationSeconds")
+ .HasColumnType("int");
+
+ b.Property("FrequencyMinutes")
+ .HasColumnType("int");
+
+ b.Property("HorizontalMarginPercent")
+ .HasColumnType("double");
+
+ b.Property("Image")
+ .HasColumnType("longtext");
+
+ b.Property("ImageSource")
+ .HasColumnType("int");
+
+ b.Property("Location")
+ .HasColumnType("int");
+
+ b.Property("Mode")
+ .HasColumnType("int");
+
+ b.Property("Name")
+ .HasColumnType("longtext");
+
+ b.Property("Opacity")
+ .HasColumnType("int");
+
+ b.Property("OpacityExpression")
+ .HasColumnType("longtext");
+
+ b.Property("OriginalContentType")
+ .HasColumnType("longtext");
+
+ b.Property("PlaceWithinSourceContent")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("Size")
+ .HasColumnType("int");
+
+ b.Property("VerticalMarginPercent")
+ .HasColumnType("double");
+
+ b.Property("WidthPercent")
+ .HasColumnType("double");
+
+ b.Property("ZIndex")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.ToTable("ChannelWatermark", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.Collection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("Name")
+ .HasColumnType("longtext");
+
+ b.Property("UseCustomPlaybackOrder")
+ .HasColumnType("tinyint(1)");
+
+ b.HasKey("Id");
+
+ b.ToTable("Collection", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.CollectionItem", b =>
+ {
+ b.Property("CollectionId")
+ .HasColumnType("int");
+
+ b.Property("MediaItemId")
+ .HasColumnType("int");
+
+ b.Property("CustomIndex")
+ .HasColumnType("int");
+
+ b.HasKey("CollectionId", "MediaItemId");
+
+ b.HasIndex("MediaItemId");
+
+ b.ToTable("CollectionItem", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.ConfigElement", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("Key")
+ .HasColumnType("varchar(255)");
+
+ b.Property("Value")
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Key")
+ .IsUnique();
+
+ b.ToTable("ConfigElement", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.DecoGraphicsElement", b =>
+ {
+ b.Property("DecoId")
+ .HasColumnType("int");
+
+ b.Property("GraphicsElementId")
+ .HasColumnType("int");
+
+ b.HasKey("DecoId", "GraphicsElementId");
+
+ b.HasIndex("GraphicsElementId");
+
+ b.ToTable("DecoGraphicsElement");
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.DecoWatermark", b =>
+ {
+ b.Property("DecoId")
+ .HasColumnType("int");
+
+ b.Property("WatermarkId")
+ .HasColumnType("int");
+
+ b.HasKey("DecoId", "WatermarkId");
+
+ b.HasIndex("WatermarkId");
+
+ b.ToTable("DecoWatermark");
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.Director", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("EpisodeMetadataId")
+ .HasColumnType("int");
+
+ b.Property("MovieMetadataId")
+ .HasColumnType("int");
+
+ b.Property("MusicVideoMetadataId")
+ .HasColumnType("int");
+
+ b.Property("Name")
+ .HasColumnType("longtext");
+
+ b.Property("OtherVideoMetadataId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("EpisodeMetadataId");
+
+ b.HasIndex("MovieMetadataId");
+
+ b.HasIndex("MusicVideoMetadataId");
+
+ b.HasIndex("OtherVideoMetadataId");
+
+ b.ToTable("Director", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyCollection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("Etag")
+ .HasColumnType("longtext");
+
+ b.Property("ItemId")
+ .HasColumnType("longtext");
+
+ b.Property("Name")
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.ToTable("EmbyCollection", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyConnection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("Address")
+ .HasColumnType("longtext");
+
+ b.Property("EmbyMediaSourceId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("EmbyMediaSourceId");
+
+ b.ToTable("EmbyConnection", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyPathReplacement", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("EmbyMediaSourceId")
+ .HasColumnType("int");
+
+ b.Property("EmbyPath")
+ .HasColumnType("longtext");
+
+ b.Property("LocalPath")
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.HasIndex("EmbyMediaSourceId");
+
+ b.ToTable("EmbyPathReplacement", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.EpisodeMetadata", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("DateAdded")
+ .HasColumnType("datetime(6)");
+
+ b.Property("DateUpdated")
+ .HasColumnType("datetime(6)");
+
+ b.Property("EpisodeId")
+ .HasColumnType("int");
+
+ b.Property("EpisodeNumber")
+ .HasColumnType("int");
+
+ b.Property("MetadataKind")
+ .HasColumnType("int");
+
+ b.Property("OriginalTitle")
+ .HasColumnType("longtext");
+
+ b.Property("Outline")
+ .HasColumnType("longtext");
+
+ b.Property("Plot")
+ .HasColumnType("longtext");
+
+ b.Property("ReleaseDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("SortTitle")
+ .HasColumnType("longtext");
+
+ b.Property("Tagline")
+ .HasColumnType("longtext");
+
+ b.Property("Title")
+ .HasColumnType("longtext");
+
+ b.Property("Year")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("EpisodeId");
+
+ b.ToTable("EpisodeMetadata", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.FFmpegProfile", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("AllowBFrames")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("AudioBitrate")
+ .HasColumnType("int");
+
+ b.Property("AudioBufferSize")
+ .HasColumnType("int");
+
+ b.Property("AudioChannels")
+ .HasColumnType("int");
+
+ b.Property("AudioFormat")
+ .HasColumnType("int");
+
+ b.Property("AudioSampleRate")
+ .HasColumnType("int");
+
+ b.Property("BitDepth")
+ .HasColumnType("int");
+
+ b.Property("DeinterlaceVideo")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("tinyint(1)")
+ .HasDefaultValue(true);
+
+ b.Property("HardwareAcceleration")
+ .HasColumnType("int");
+
+ b.Property("Name")
+ .HasColumnType("longtext");
+
+ b.Property("NormalizeFramerate")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("tinyint(1)")
+ .HasDefaultValue(false);
+
+ b.Property("NormalizeLoudnessMode")
+ .HasColumnType("int");
+
+ b.Property("QsvExtraHardwareFrames")
+ .HasColumnType("int");
+
+ b.Property("ResolutionId")
+ .HasColumnType("int");
+
+ b.Property("ScalingBehavior")
+ .HasColumnType("int");
+
+ b.Property("ThreadCount")
+ .HasColumnType("int");
+
+ b.Property("TonemapAlgorithm")
+ .HasColumnType("int");
+
+ b.Property("VaapiDevice")
+ .HasColumnType("longtext");
+
+ b.Property("VaapiDisplay")
+ .HasColumnType("longtext");
+
+ b.Property("VaapiDriver")
+ .HasColumnType("int");
+
+ b.Property("VideoBitrate")
+ .HasColumnType("int");
+
+ b.Property("VideoBufferSize")
+ .HasColumnType("int");
+
+ b.Property("VideoFormat")
+ .HasColumnType("int");
+
+ b.Property("VideoPreset")
+ .HasColumnType("longtext");
+
+ b.Property("VideoProfile")
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ResolutionId");
+
+ b.ToTable("FFmpegProfile", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.Filler.FillerPreset", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("AllowWatermarks")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("CollectionId")
+ .HasColumnType("int");
+
+ b.Property("CollectionType")
+ .HasColumnType("int");
+
+ b.Property("Count")
+ .HasColumnType("int");
+
+ b.Property("Duration")
+ .HasColumnType("time(6)");
+
+ b.Property("Expression")
+ .HasColumnType("longtext");
+
+ b.Property("FillerKind")
+ .HasColumnType("int");
+
+ b.Property("FillerMode")
+ .HasColumnType("int");
+
+ b.Property("MediaItemId")
+ .HasColumnType("int");
+
+ b.Property("MultiCollectionId")
+ .HasColumnType("int");
+
+ b.Property("Name")
+ .HasColumnType("longtext");
+
+ b.Property("PadToNearestMinute")
+ .HasColumnType("int");
+
+ b.Property("PlaylistId")
+ .HasColumnType("int");
+
+ b.Property("SmartCollectionId")
+ .HasColumnType("int");
+
+ b.Property("UseChaptersAsMediaItems")
+ .HasColumnType("tinyint(1)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CollectionId");
+
+ b.HasIndex("MediaItemId");
+
+ b.HasIndex("MultiCollectionId");
+
+ b.HasIndex("PlaylistId");
+
+ b.HasIndex("SmartCollectionId");
+
+ b.ToTable("FillerPreset", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.Genre", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("ArtistMetadataId")
+ .HasColumnType("int");
+
+ b.Property("EpisodeMetadataId")
+ .HasColumnType("int");
+
+ b.Property("ImageMetadataId")
+ .HasColumnType("int");
+
+ b.Property("MovieMetadataId")
+ .HasColumnType("int");
+
+ b.Property("MusicVideoMetadataId")
+ .HasColumnType("int");
+
+ b.Property("Name")
+ .HasColumnType("longtext");
+
+ b.Property("OtherVideoMetadataId")
+ .HasColumnType("int");
+
+ b.Property("RemoteStreamMetadataId")
+ .HasColumnType("int");
+
+ b.Property("SeasonMetadataId")
+ .HasColumnType("int");
+
+ b.Property("ShowMetadataId")
+ .HasColumnType("int");
+
+ b.Property("SongMetadataId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ArtistMetadataId");
+
+ b.HasIndex("EpisodeMetadataId");
+
+ b.HasIndex("ImageMetadataId");
+
+ b.HasIndex("MovieMetadataId");
+
+ b.HasIndex("MusicVideoMetadataId");
+
+ b.HasIndex("OtherVideoMetadataId");
+
+ b.HasIndex("RemoteStreamMetadataId");
+
+ b.HasIndex("SeasonMetadataId");
+
+ b.HasIndex("ShowMetadataId");
+
+ b.HasIndex("SongMetadataId");
+
+ b.ToTable("Genre");
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.GraphicsElement", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("Kind")
+ .HasColumnType("int");
+
+ b.Property("Name")
+ .HasColumnType("longtext");
+
+ b.Property("Path")
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.ToTable("GraphicsElement", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.ImageFolderDuration", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("DurationSeconds")
+ .HasColumnType("double");
+
+ b.Property("LibraryFolderId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LibraryFolderId")
+ .IsUnique();
+
+ b.ToTable("ImageFolderDuration", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.ImageMetadata", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("DateAdded")
+ .HasColumnType("datetime(6)");
+
+ b.Property("DateUpdated")
+ .HasColumnType("datetime(6)");
+
+ b.Property("DurationSeconds")
+ .HasColumnType("double");
+
+ b.Property("ImageId")
+ .HasColumnType("int");
+
+ b.Property("MetadataKind")
+ .HasColumnType("int");
+
+ b.Property("OriginalTitle")
+ .HasColumnType("longtext");
+
+ b.Property("ReleaseDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("SortTitle")
+ .HasColumnType("longtext");
+
+ b.Property("Title")
+ .HasColumnType("longtext");
+
+ b.Property("Year")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ImageId");
+
+ b.ToTable("ImageMetadata", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinCollection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("Etag")
+ .HasColumnType("longtext");
+
+ b.Property("ItemId")
+ .HasColumnType("longtext");
+
+ b.Property("Name")
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.ToTable("JellyfinCollection", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinConnection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("Address")
+ .HasColumnType("longtext");
+
+ b.Property("JellyfinMediaSourceId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("JellyfinMediaSourceId");
+
+ b.ToTable("JellyfinConnection", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinPathReplacement", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property