diff --git a/CHANGELOG.md b/CHANGELOG.md
index 729c80d67..0f1293dc2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -106,10 +106,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Split main `Settings` page into multiple pages
- Update UI layout on all pages to be less cramped and to work better on mobile
- Add CPU and Video Controller info to `Troubleshooting` > `General` output
-- Expand special zero-count case for `Multiple` playout mode with playlists
- - This configuration will automatically maintain the multiple count so that it is equal to the number of items in each playlist item
- - This configuration should be used if you want to play every media item in a playlist item exactly once before advancing
- Enable write-ahead logging (WAL) mode on SQLite databases
+- Add `Multiple Mode` option to schedule items editor and remove support for count values of zero
+ - `Count`: same behavior as before, requires a number of media items to play and will always schedule the same number
+ - `Collection Size`: similar to count of zero before, will play all media items from the collection before continuing to the next schedule item
+ - `Playlist Item Size`: will play all media items from the current playlist item before continuing to the next schedule item
### Fixed
- Fix QSV acceleration in docker with older Intel devices
diff --git a/ErsatzTV.Application/ProgramSchedules/Commands/AddProgramScheduleItem.cs b/ErsatzTV.Application/ProgramSchedules/Commands/AddProgramScheduleItem.cs
index 2791e3963..896eb08d2 100644
--- a/ErsatzTV.Application/ProgramSchedules/Commands/AddProgramScheduleItem.cs
+++ b/ErsatzTV.Application/ProgramSchedules/Commands/AddProgramScheduleItem.cs
@@ -18,6 +18,7 @@ public record AddProgramScheduleItem(
int? PlaylistId,
PlaybackOrder PlaybackOrder,
FillWithGroupMode FillWithGroupMode,
+ MultipleMode MultipleMode,
int? MultipleCount,
TimeSpan? PlayoutDuration,
TailMode TailMode,
diff --git a/ErsatzTV.Application/ProgramSchedules/Commands/IProgramScheduleItemRequest.cs b/ErsatzTV.Application/ProgramSchedules/Commands/IProgramScheduleItemRequest.cs
index 0fda73636..19e7bd1df 100644
--- a/ErsatzTV.Application/ProgramSchedules/Commands/IProgramScheduleItemRequest.cs
+++ b/ErsatzTV.Application/ProgramSchedules/Commands/IProgramScheduleItemRequest.cs
@@ -16,6 +16,7 @@ public interface IProgramScheduleItemRequest
PlayoutMode PlayoutMode { get; }
PlaybackOrder PlaybackOrder { get; }
FillWithGroupMode FillWithGroupMode { get; }
+ MultipleMode MultipleMode { get; }
int? MultipleCount { get; }
TimeSpan? PlayoutDuration { get; }
TailMode TailMode { get; }
diff --git a/ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs b/ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs
index 395143c30..34f5795c1 100644
--- a/ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs
+++ b/ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs
@@ -71,10 +71,17 @@ public abstract class ProgramScheduleItemCommandBase
case PlayoutMode.One:
break;
case PlayoutMode.Multiple:
- if (item.MultipleCount.GetValueOrDefault() < 0)
+ if (item.MultipleMode is MultipleMode.PlaylistItemSize &&
+ item.CollectionType is not ProgramScheduleItemCollectionType.Playlist)
{
return BaseError.New(
- "[MultipleCount] must be greater than or equal to 0 for playout mode 'multiple'");
+ "[MultipleMode] cannot be [PlaylistItemSize] when collection is not a playlist");
+ }
+
+ if (item.MultipleMode is MultipleMode.Count && item.MultipleCount.GetValueOrDefault() < 1)
+ {
+ return BaseError.New(
+ "[MultipleCount] must be greater than 0 for playout mode 'multiple / count'");
}
break;
@@ -248,7 +255,8 @@ public abstract class ProgramScheduleItemCommandBase
PlaylistId = item.PlaylistId,
PlaybackOrder = item.PlaybackOrder,
FillWithGroupMode = item.FillWithGroupMode,
- Count = item.MultipleCount.GetValueOrDefault(),
+ MultipleMode = item.MultipleMode,
+ Count = item.MultipleMode is MultipleMode.Count ? item.MultipleCount.GetValueOrDefault() : 0,
CustomTitle = item.CustomTitle,
GuideMode = item.GuideMode,
PreRollFillerId = item.PreRollFillerId,
diff --git a/ErsatzTV.Application/ProgramSchedules/Commands/ReplaceProgramScheduleItems.cs b/ErsatzTV.Application/ProgramSchedules/Commands/ReplaceProgramScheduleItems.cs
index 0ab7e3094..a1ea20abd 100644
--- a/ErsatzTV.Application/ProgramSchedules/Commands/ReplaceProgramScheduleItems.cs
+++ b/ErsatzTV.Application/ProgramSchedules/Commands/ReplaceProgramScheduleItems.cs
@@ -18,6 +18,7 @@ public record ReplaceProgramScheduleItem(
int? PlaylistId,
PlaybackOrder PlaybackOrder,
FillWithGroupMode FillWithGroupMode,
+ MultipleMode MultipleMode,
int? MultipleCount,
TimeSpan? PlayoutDuration,
TailMode TailMode,
diff --git a/ErsatzTV.Application/ProgramSchedules/Mapper.cs b/ErsatzTV.Application/ProgramSchedules/Mapper.cs
index 29fe17066..5ed80a241 100644
--- a/ErsatzTV.Application/ProgramSchedules/Mapper.cs
+++ b/ErsatzTV.Application/ProgramSchedules/Mapper.cs
@@ -155,6 +155,7 @@ internal static class Mapper
},
multiple.PlaybackOrder,
multiple.FillWithGroupMode,
+ multiple.MultipleMode,
multiple.Count,
multiple.CustomTitle,
multiple.GuideMode,
diff --git a/ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs b/ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs
index a647f20c7..4272b8ecb 100644
--- a/ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs
+++ b/ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs
@@ -23,6 +23,7 @@ public record ProgramScheduleItemMultipleViewModel : ProgramScheduleItemViewMode
NamedMediaItemViewModel mediaItem,
PlaybackOrder playbackOrder,
FillWithGroupMode fillWithGroupMode,
+ MultipleMode multipleMode,
int count,
string customTitle,
GuideMode guideMode,
@@ -61,8 +62,13 @@ public record ProgramScheduleItemMultipleViewModel : ProgramScheduleItemViewMode
preferredAudioLanguageCode,
preferredAudioTitle,
preferredSubtitleLanguageCode,
- subtitleMode) =>
+ subtitleMode)
+ {
+ MultipleMode = multipleMode;
Count = count;
+ }
+
+ public MultipleMode MultipleMode { get; set; }
public int Count { get; }
}
diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs
index b361cc716..d15428977 100644
--- a/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs
+++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs
@@ -1503,7 +1503,7 @@ public class PlayoutBuilderTests
}
[Test]
- public async Task Auto_Zero_MultipleCount()
+ public async Task Multiple_Mode_Collection_Size()
{
var collectionOne = new Collection
{
@@ -1543,6 +1543,7 @@ public class PlayoutBuilderTests
CollectionId = collectionOne.Id,
StartTime = null,
Count = 0,
+ MultipleMode = MultipleMode.CollectionSize,
PlaybackOrder = PlaybackOrder.Chronological
},
new ProgramScheduleItemMultiple
@@ -1553,6 +1554,7 @@ public class PlayoutBuilderTests
CollectionId = collectionTwo.Id,
StartTime = null,
Count = 0,
+ MultipleMode = MultipleMode.CollectionSize,
PlaybackOrder = PlaybackOrder.Chronological
}
};
diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerMultipleTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerMultipleTests.cs
index 7f14d211f..f4d58ee50 100644
--- a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerMultipleTests.cs
+++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerMultipleTests.cs
@@ -33,6 +33,7 @@ public class PlayoutModeSchedulerMultipleTests : SchedulerTestBase
TailFiller = null,
FallbackFiller = null,
Count = 0,
+ MultipleMode = MultipleMode.CollectionSize,
CustomTitle = "CustomTitle"
};
diff --git a/ErsatzTV.Core/Domain/MultipleMode.cs b/ErsatzTV.Core/Domain/MultipleMode.cs
new file mode 100644
index 000000000..9ff4472af
--- /dev/null
+++ b/ErsatzTV.Core/Domain/MultipleMode.cs
@@ -0,0 +1,17 @@
+namespace ErsatzTV.Core.Domain;
+
+public enum MultipleMode
+{
+ // static integer count
+ Count = 0,
+
+ // current size of the collection
+ CollectionSize = 1,
+
+ // current size of the playlist item
+ PlaylistItemSize = 2,
+
+ // from one item (not a multi-episode) to however many multi-episodes are linked together
+ // is this limited to chronological and season/episode?
+ MultiEpisodeSize = 3
+}
diff --git a/ErsatzTV.Core/Domain/ProgramScheduleItemMultiple.cs b/ErsatzTV.Core/Domain/ProgramScheduleItemMultiple.cs
index b83990987..383bb16e5 100644
--- a/ErsatzTV.Core/Domain/ProgramScheduleItemMultiple.cs
+++ b/ErsatzTV.Core/Domain/ProgramScheduleItemMultiple.cs
@@ -2,5 +2,7 @@
public class ProgramScheduleItemMultiple : ProgramScheduleItem
{
+ public MultipleMode MultipleMode { get; set; }
+
public int Count { get; set; }
}
diff --git a/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs b/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs
index 4c11d533a..614f9eb77 100644
--- a/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs
+++ b/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs
@@ -42,22 +42,25 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase
+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("20250718133418_Add_ProgramScheduleItemMultipleMode")]
+ partial class Add_ProgramScheduleItemMultipleMode
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.7")
+ .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("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("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("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("SeasonMetadataId");
+
+ b.HasIndex("ShowMetadataId");
+
+ b.HasIndex("SongMetadataId");
+
+ b.ToTable("Artwork", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("ActiveMode")
+ .HasColumnType("int");
+
+ 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("MusicVideoCreditsMode")
+ .HasColumnType("int");
+
+ b.Property("MusicVideoCreditsTemplate")
+ .HasColumnType("longtext");
+
+ b.Property("Name")
+ .HasColumnType("longtext");
+
+ b.Property("Number")
+ .HasColumnType("varchar(255)");
+
+ b.Property("PreferredAudioLanguageCode")
+ .HasColumnType("longtext");
+
+ b.Property("PreferredAudioTitle")
+ .HasColumnType("longtext");
+
+ b.Property("PreferredSubtitleLanguageCode")
+ .HasColumnType("longtext");
+
+ b.Property("ProgressMode")
+ .HasColumnType("int");
+
+ b.Property("SongVideoMode")
+ .HasColumnType("int");
+
+ b.Property("StreamSelector")
+ .HasColumnType("longtext");
+
+ b.Property("StreamSelectorMode")
+ .HasColumnType("int");
+
+ b.Property("StreamingMode")
+ .HasColumnType("int");
+
+ b.Property("SubtitleMode")
+ .HasColumnType("int");
+
+ b.Property("UniqueId")
+ .HasColumnType("char(36)");
+
+ b.Property("WatermarkId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("FFmpegProfileId");
+
+ b.HasIndex("FallbackFillerId");
+
+ 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("int");
+
+ 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("OriginalContentType")
+ .HasColumnType("longtext");
+
+ b.Property("PlaceWithinSourceContent")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("Size")
+ .HasColumnType("int");
+
+ b.Property("VerticalMarginPercent")
+ .HasColumnType("int");
+
+ b.Property("WidthPercent")
+ .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.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("SmartCollectionId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CollectionId");
+
+ b.HasIndex("MediaItemId");
+
+ b.HasIndex("MultiCollectionId");
+
+ 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("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("SeasonMetadataId");
+
+ b.HasIndex("ShowMetadataId");
+
+ b.HasIndex("SongMetadataId");
+
+ b.ToTable("Genre");
+ });
+
+ 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("Id"));
+
+ b.Property("JellyfinMediaSourceId")
+ .HasColumnType("int");
+
+ b.Property("JellyfinPath")
+ .HasColumnType("longtext");
+
+ b.Property("LocalPath")
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.HasIndex("JellyfinMediaSourceId");
+
+ b.ToTable("JellyfinPathReplacement", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.LanguageCode", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("EnglishName")
+ .HasColumnType("longtext");
+
+ b.Property("FrenchName")
+ .HasColumnType("longtext");
+
+ b.Property("ThreeCode1")
+ .HasColumnType("longtext");
+
+ b.Property("ThreeCode2")
+ .HasColumnType("longtext");
+
+ b.Property("TwoCode")
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.ToTable("LanguageCode", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("LastScan")
+ .HasColumnType("datetime(6)");
+
+ b.Property("MediaKind")
+ .HasColumnType("int");
+
+ b.Property("MediaSourceId")
+ .HasColumnType("int");
+
+ b.Property("Name")
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MediaSourceId");
+
+ b.ToTable("Library", (string)null);
+
+ b.UseTptMappingStrategy();
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryFolder", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("Etag")
+ .HasColumnType("longtext");
+
+ b.Property("LibraryPathId")
+ .HasColumnType("int");
+
+ b.Property("ParentId")
+ .HasColumnType("int");
+
+ b.Property("Path")
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LibraryPathId");
+
+ b.HasIndex("ParentId");
+
+ b.ToTable("LibraryFolder", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("LastScan")
+ .HasColumnType("datetime(6)");
+
+ b.Property("LibraryId")
+ .HasColumnType("int");
+
+ b.Property("Path")
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LibraryId");
+
+ b.ToTable("LibraryPath", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.MediaChapter", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("ChapterId")
+ .HasColumnType("bigint");
+
+ b.Property("EndTime")
+ .HasColumnType("time(6)");
+
+ b.Property("MediaVersionId")
+ .HasColumnType("int");
+
+ b.Property("StartTime")
+ .HasColumnType("time(6)");
+
+ b.Property("Title")
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MediaVersionId");
+
+ b.ToTable("MediaChapter", (string)null);
+ });
+
+ modelBuilder.Entity("ErsatzTV.Core.Domain.MediaFile", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("LibraryFolderId")
+ .HasColumnType("int");
+
+ b.Property("MediaVersionId")
+ .HasColumnType("int");
+
+ b.Property