add dead air fallback to deco system (#1668)

* add dead air fallback to deco editor

* fix deco watermark logic

* use dead air fallback from decos
This commit is contained in:
Jason Dove
2024-04-05 23:11:14 -05:00
committed by GitHub
parent 2e2523c380
commit e305222141
19 changed files with 17474 additions and 86 deletions
+5 -3
View File
@@ -9,14 +9,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- This is year-agnostic, meaning the Month/Day range will apply to every year - This is year-agnostic, meaning the Month/Day range will apply to every year
- This also supports wrapping the end of the year (e.g., start 12/1 and end 1/15) - This also supports wrapping the end of the year (e.g., start 12/1 and end 1/15)
- Add new `Deco` system for "decorating" channels with non-primary content - Add new `Deco` system for "decorating" channels with non-primary content
- Decos currently only contain Watermarks, but future work will add other functionality, including filler - Decos currently contain
- Watermarks
- Dead Air Fallback (i.e. fallback filler)
- Similar to blocks, decos have deco groups for organization - Similar to blocks, decos have deco groups for organization
- Similar to blocks, decos have deco templates for filling a "day" with decos - Similar to blocks, decos have deco templates for filling a "day" with decos
- In the playout template editor, playout template items can have *both* a block template and a deco template - In the playout template editor, playout template items can have *both* a block template and a deco template
- This allows watermarks to change at different times than primary content - This allows watermarks and dead air fallback to change at different times than primary content
- Block playouts can also have a default deco - Block playouts can also have a default deco
- This will apply whenever a deco template is missing, or when a deco template item cannot be found for the current time - This will apply whenever a deco template is missing, or when a deco template item cannot be found for the current time
- Effectively, this sets a default watermark for the entire playout - Effectively, this sets a default watermark and dead air fallback for the entire playout
### Fixed ### Fixed
- Fix some cases of 404s from Plex when files were replaced and scanning the library from ETV didn't help - Fix some cases of 404s from Plex when files were replaced and scanning the library from ETV didn't help
@@ -1,5 +1,19 @@
using ErsatzTV.Core; using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling;
namespace ErsatzTV.Application.Scheduling; namespace ErsatzTV.Application.Scheduling;
public record UpdateDeco(int DecoId, int DecoGroupId, string Name, int? WatermarkId) : IRequest<Either<BaseError, DecoViewModel>>; public record UpdateDeco(
int DecoId,
int DecoGroupId,
string Name,
DecoMode WatermarkMode,
int? WatermarkId,
DecoMode DeadAirFallbackMode,
ProgramScheduleItemCollectionType DeadAirFallbackCollectionType,
int? DeadAirFallbackCollectionId,
int? DeadAirFallbackMediaItemId,
int? DeadAirFallbackMultiCollectionId,
int? DeadAirFallbackSmartCollectionId)
: IRequest<Either<BaseError, DecoViewModel>>;
@@ -22,7 +22,26 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory)
UpdateDeco request) UpdateDeco request)
{ {
existing.Name = request.Name; existing.Name = request.Name;
existing.WatermarkId = request.WatermarkId;
// watermark
existing.WatermarkMode = request.WatermarkMode;
existing.WatermarkId = request.WatermarkMode is DecoMode.Override ? request.WatermarkId : null;
// dead air fallback
existing.DeadAirFallbackMode = request.DeadAirFallbackMode;
existing.DeadAirFallbackCollectionType = request.DeadAirFallbackCollectionType;
existing.DeadAirFallbackCollectionId = request.DeadAirFallbackMode is DecoMode.Override
? request.DeadAirFallbackCollectionId
: null;
existing.DeadAirFallbackMediaItemId = request.DeadAirFallbackMode is DecoMode.Override
? request.DeadAirFallbackMediaItemId
: null;
existing.DeadAirFallbackMultiCollectionId = request.DeadAirFallbackMode is DecoMode.Override
? request.DeadAirFallbackMultiCollectionId
: null;
existing.DeadAirFallbackSmartCollectionId = request.DeadAirFallbackMode is DecoMode.Override
? request.DeadAirFallbackSmartCollectionId
: null;
await dbContext.SaveChangesAsync(); await dbContext.SaveChangesAsync();
@@ -1,3 +1,17 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling;
namespace ErsatzTV.Application.Scheduling; namespace ErsatzTV.Application.Scheduling;
public record DecoViewModel(int Id, int DecoGroupId, string Name, int? WatermarkId); public record DecoViewModel(
int Id,
int DecoGroupId,
string Name,
DecoMode WatermarkMode,
int? WatermarkId,
DecoMode DeadAirFallbackMode,
ProgramScheduleItemCollectionType DeadAirFallbackCollectionType,
int? DeadAirFallbackCollectionId,
int? DeadAirFallbackMediaItemId,
int? DeadAirFallbackMultiCollectionId,
int? DeadAirFallbackSmartCollectionId);
+12 -1
View File
@@ -50,7 +50,18 @@ internal static class Mapper
new(decoGroup.Id, decoGroup.Name, decoGroup.Decos.Count); new(decoGroup.Id, decoGroup.Name, decoGroup.Decos.Count);
internal static DecoViewModel ProjectToViewModel(Deco deco) => internal static DecoViewModel ProjectToViewModel(Deco deco) =>
new(deco.Id, deco.DecoGroupId, deco.Name, deco.WatermarkId); new(
deco.Id,
deco.DecoGroupId,
deco.Name,
deco.WatermarkMode,
deco.WatermarkId,
deco.DeadAirFallbackMode,
deco.DeadAirFallbackCollectionType,
deco.DeadAirFallbackCollectionId,
deco.DeadAirFallbackMediaItemId,
deco.DeadAirFallbackMultiCollectionId,
deco.DeadAirFallbackSmartCollectionId);
internal static DecoTemplateGroupViewModel ProjectToViewModel(DecoTemplateGroup decoTemplateGroup) => internal static DecoTemplateGroupViewModel ProjectToViewModel(DecoTemplateGroup decoTemplateGroup) =>
new(decoTemplateGroup.Id, decoTemplateGroup.Name, decoTemplateGroup.DecoTemplates.Count); new(decoTemplateGroup.Id, decoTemplateGroup.Name, decoTemplateGroup.DecoTemplates.Count);
@@ -79,9 +79,19 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
DateTimeOffset now = request.Now; DateTimeOffset now = request.Now;
Either<BaseError, PlayoutItemWithPath> maybePlayoutItem = await dbContext.PlayoutItems Either<BaseError, PlayoutItemWithPath> maybePlayoutItem = await dbContext.PlayoutItems
// get playout deco
.Include(i => i.Playout) .Include(i => i.Playout)
.ThenInclude(p => p.Deco) .ThenInclude(p => p.Deco)
.ThenInclude(d => d.Watermark) .ThenInclude(d => d.Watermark)
// get playout templates (and deco templates/decos)
.Include(i => i.Playout)
.ThenInclude(p => p.Templates)
.ThenInclude(t => t.DecoTemplate)
.ThenInclude(t => t.Items)
.ThenInclude(i => i.Deco)
.ThenInclude(d => d.Watermark)
.Include(i => i.MediaItem) .Include(i => i.MediaItem)
.ThenInclude(mi => (mi as Episode).EpisodeMetadata) .ThenInclude(mi => (mi as Episode).EpisodeMetadata)
.ThenInclude(em => em.Subtitles) .ThenInclude(em => em.Subtitles)
@@ -163,7 +173,31 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
if (maybePlayoutItem.LeftAsEnumerable().Any(e => e is UnableToLocatePlayoutItem)) if (maybePlayoutItem.LeftAsEnumerable().Any(e => e is UnableToLocatePlayoutItem))
{ {
maybePlayoutItem = await CheckForFallbackFiller(dbContext, channel, now); Option<Playout> maybePlayout = await dbContext.Playouts
.AsNoTracking()
// get playout deco
.Include(p => p.Deco)
.ThenInclude(d => d.Watermark)
// get playout templates (and deco templates/decos)
.Include(p => p.Templates)
.ThenInclude(t => t.DecoTemplate)
.ThenInclude(t => t.Items)
.ThenInclude(i => i.Deco)
.ThenInclude(d => d.Watermark)
.SelectOneAsync(p => p.ChannelId, p => p.ChannelId == channel.Id);
foreach (var playout in maybePlayout)
{
maybePlayoutItem = await CheckForFallbackFiller(dbContext, channel, playout, now);
}
if (maybePlayout.IsNone)
{
maybePlayoutItem = await CheckForFallbackFiller(dbContext, channel, null, now);
}
} }
foreach (PlayoutItemWithPath playoutItemWithPath in maybePlayoutItem.RightToSeq()) foreach (PlayoutItemWithPath playoutItemWithPath in maybePlayoutItem.RightToSeq())
@@ -198,62 +232,20 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
watermarkId => dbContext.ChannelWatermarks watermarkId => dbContext.ChannelWatermarks
.SelectOneAsync(w => w.Id, w => w.Id == watermarkId)); .SelectOneAsync(w => w.Id, w => w.Id == watermarkId));
Option<ChannelWatermark> playoutItemWatermark = Optional(playoutItemWithPath.PlayoutItem.Watermark); Option<ChannelWatermark> playoutItemWatermark = Option<ChannelWatermark>.None;
if (playoutItemWatermark.IsNone && bool disableWatermarks = playoutItemWithPath.PlayoutItem.DisableWatermarks;
playoutItemWithPath.PlayoutItem.Playout.ProgramSchedulePlayoutType is ProgramSchedulePlayoutType.Block) WatermarkResult watermarkResult = GetPlayoutItemWatermark(playoutItemWithPath.PlayoutItem.Playout, now);
switch (watermarkResult)
{ {
_logger.LogDebug("Block playout item has no watermark; checking for deco"); case InheritWatermark:
// do nothing, other code will fall back to channel/global
// check for playout template deco break;
// load all playout templates case DisableWatermark:
// get playout template for start time disableWatermarks = true;
// check for deco break;
// load all templates that have decos case CustomWatermark watermark:
List<PlayoutTemplate> playoutTemplates = await dbContext.PlayoutTemplates playoutItemWatermark = watermark.Watermark;
.AsNoTracking() break;
.Filter(t => t.PlayoutId == playoutItemWithPath.PlayoutItem.PlayoutId)
.Filter(t => t.DecoTemplateId != null)
.Include(t => t.DecoTemplate)
.ThenInclude(t => t.Items)
.ThenInclude(i => i.Deco)
.ThenInclude(d => d.Watermark)
.ToListAsync(cancellationToken);
Option<PlayoutTemplate> maybeActiveTemplate = PlayoutTemplateSelector.GetPlayoutTemplateFor(
playoutTemplates,
playoutItemWithPath.PlayoutItem.StartOffset);
foreach (PlayoutTemplate activeTemplate in maybeActiveTemplate)
{
_logger.LogDebug("Block playout has active playout template; checking for deco template items");
Option<DecoTemplateItem> maybeItem = activeTemplate.DecoTemplate.Items
.Find(i => i.StartTime <= now.TimeOfDay && i.EndTime > now.TimeOfDay);
foreach (DecoTemplateItem item in maybeItem)
{
_logger.LogDebug("Block playout has active deco template item; checking for watermark");
foreach (ChannelWatermark watermark in Optional(item.Deco.Watermark))
{
_logger.LogDebug(
"Block playout has active deco template item with watermark; will use for this playout item");
playoutItemWatermark = watermark;
}
}
}
if (playoutItemWatermark.IsNone)
{
// check for playout deco
foreach (Deco deco in Optional(playoutItemWithPath.PlayoutItem.Playout.Deco))
{
_logger.LogDebug("Block playout item has default deco; checking for watermark");
foreach (ChannelWatermark watermark in Optional(deco.Watermark))
{
_logger.LogDebug(
"Block playout has default deco with watermark; will use for this playout item");
playoutItemWatermark = watermark;
}
}
}
} }
if (playoutItemWithPath.PlayoutItem.MediaItem is Song song) if (playoutItemWithPath.PlayoutItem.MediaItem is Song song)
@@ -312,7 +304,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
outPoint, outPoint,
request.PtsOffset, request.PtsOffset,
request.TargetFramerate, request.TargetFramerate,
playoutItemWithPath.PlayoutItem.DisableWatermarks, disableWatermarks,
_ => { }); _ => { });
var result = new PlayoutItemProcessModel(process, duration, finish, true); var result = new PlayoutItemProcessModel(process, duration, finish, true);
@@ -463,20 +455,44 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
private async Task<Either<BaseError, PlayoutItemWithPath>> CheckForFallbackFiller( private async Task<Either<BaseError, PlayoutItemWithPath>> CheckForFallbackFiller(
TvContext dbContext, TvContext dbContext,
Channel channel, Channel channel,
Playout playout,
DateTimeOffset now) DateTimeOffset now)
{ {
// check for channel fallback Option<FillerPreset> maybeFallback = Option<FillerPreset>.None;
Option<FillerPreset> maybeFallback = await dbContext.FillerPresets
.SelectOneAsync(w => w.Id, w => w.Id == channel.FallbackFillerId);
// then check for global fallback DeadAirFallbackResult decoDeadAirFallback = GetDecoDeadAirFallback(playout, now);
if (maybeFallback.IsNone) switch (decoDeadAirFallback)
{ {
maybeFallback = await dbContext.ConfigElements case CustomDeadAirFallback custom:
.GetValue<int>(ConfigElementKey.FFmpegGlobalFallbackFillerId) maybeFallback = new FillerPreset
.BindT(fillerId => dbContext.FillerPresets.SelectOneAsync(w => w.Id, w => w.Id == fillerId)); {
AllowWatermarks = true,
CollectionType = custom.CollectionType,
CollectionId = custom.CollectionId,
MediaItemId = custom.MediaItemId,
MultiCollectionId = custom.MultiCollectionId,
SmartCollectionId = custom.SmartCollectionId
};
break;
case DisableDeadAirFallback:
// do nothing
break;
case InheritDeadAirFallback:
// check for channel fallback
maybeFallback = await dbContext.FillerPresets
.SelectOneAsync(w => w.Id, w => w.Id == channel.FallbackFillerId);
// then check for global fallback
if (maybeFallback.IsNone)
{
maybeFallback = await dbContext.ConfigElements
.GetValue<int>(ConfigElementKey.FFmpegGlobalFallbackFillerId)
.BindT(fillerId => dbContext.FillerPresets.SelectOneAsync(w => w.Id, w => w.Id == fillerId));
}
break;
} }
foreach (FillerPreset fallbackPreset in maybeFallback) foreach (FillerPreset fallbackPreset in maybeFallback)
{ {
// turn this into a playout item // turn this into a playout item
@@ -641,4 +657,140 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
_ => path _ => path
}; };
} }
private WatermarkResult GetPlayoutItemWatermark(Playout playout, DateTimeOffset now)
{
DecoEntries decoEntries = GetDecoEntries(playout, now);
// first, check deco template / active deco
foreach (Deco templateDeco in decoEntries.TemplateDeco)
{
switch (templateDeco.WatermarkMode)
{
case DecoMode.Override:
_logger.LogDebug("Watermark will come from template deco (override)");
return new CustomWatermark(templateDeco.Watermark);
case DecoMode.Disable:
_logger.LogDebug("Watermark is disabled by template deco");
return new DisableWatermark();
case DecoMode.Inherit:
_logger.LogDebug("Watermark will inherit from playout deco");
break;
}
}
// second, check playout deco
foreach (Deco playoutDeco in decoEntries.PlayoutDeco)
{
switch (playoutDeco.WatermarkMode)
{
case DecoMode.Override:
_logger.LogDebug("Watermark will come from playout deco (override)");
return new CustomWatermark(playoutDeco.Watermark);
case DecoMode.Disable:
_logger.LogDebug("Watermark is disabled by playout deco");
return new DisableWatermark();
case DecoMode.Inherit:
_logger.LogDebug("Watermark will inherit from channel and/or global setting");
break;
}
}
return new InheritWatermark();
}
private DeadAirFallbackResult GetDecoDeadAirFallback(Playout playout, DateTimeOffset now)
{
DecoEntries decoEntries = GetDecoEntries(playout, now);
// first, check deco template / active deco
foreach (Deco templateDeco in decoEntries.TemplateDeco)
{
switch (templateDeco.DeadAirFallbackMode)
{
case DecoMode.Override:
_logger.LogDebug("Dead air fallback will come from template deco (override)");
return new CustomDeadAirFallback(
templateDeco.DeadAirFallbackCollectionType,
templateDeco.DeadAirFallbackCollectionId,
templateDeco.DeadAirFallbackMediaItemId,
templateDeco.DeadAirFallbackMultiCollectionId,
templateDeco.DeadAirFallbackSmartCollectionId);
case DecoMode.Disable:
_logger.LogDebug("Dead air fallback is disabled by template deco");
return new DisableDeadAirFallback();
case DecoMode.Inherit:
_logger.LogDebug("Dead air fallback will inherit from playout deco");
break;
}
}
// second, check playout deco
foreach (Deco playoutDeco in decoEntries.PlayoutDeco)
{
switch (playoutDeco.DeadAirFallbackMode)
{
case DecoMode.Override:
_logger.LogDebug("Dead air fallback will come from playout deco (override)");
return new CustomDeadAirFallback(
playoutDeco.DeadAirFallbackCollectionType,
playoutDeco.DeadAirFallbackCollectionId,
playoutDeco.DeadAirFallbackMediaItemId,
playoutDeco.DeadAirFallbackMultiCollectionId,
playoutDeco.DeadAirFallbackSmartCollectionId);
case DecoMode.Disable:
_logger.LogDebug("Dead air fallback is disabled by playout deco");
return new DisableDeadAirFallback();
case DecoMode.Inherit:
_logger.LogDebug("Dead air fallback will inherit from channel and/or global setting");
break;
}
}
return new InheritDeadAirFallback();
}
private static DecoEntries GetDecoEntries(Playout playout, DateTimeOffset now)
{
if (playout is null)
{
return new DecoEntries(Option<Deco>.None, Option<Deco>.None);
}
Option<Deco> maybePlayoutDeco = Optional(playout.Deco);
Option<Deco> maybeTemplateDeco = Option<Deco>.None;
Option<PlayoutTemplate> maybeActiveTemplate =
PlayoutTemplateSelector.GetPlayoutTemplateFor(playout.Templates, now);
foreach (PlayoutTemplate activeTemplate in maybeActiveTemplate)
{
Option<DecoTemplateItem> maybeItem = activeTemplate.DecoTemplate.Items
.Find(i => i.StartTime <= now.TimeOfDay && i.EndTime > now.TimeOfDay);
foreach (DecoTemplateItem item in maybeItem)
{
maybeTemplateDeco = Optional(item.Deco);
}
}
return new DecoEntries(maybeTemplateDeco, maybePlayoutDeco);
}
private sealed record DecoEntries(Option<Deco> TemplateDeco, Option<Deco> PlayoutDeco);
private abstract record WatermarkResult;
private sealed record InheritWatermark : WatermarkResult;
private sealed record DisableWatermark : WatermarkResult;
private sealed record CustomWatermark(ChannelWatermark Watermark) : WatermarkResult;
private abstract record DeadAirFallbackResult;
private sealed record InheritDeadAirFallback : DeadAirFallbackResult;
private sealed record DisableDeadAirFallback : DeadAirFallbackResult;
private sealed record CustomDeadAirFallback(
ProgramScheduleItemCollectionType CollectionType,
int? CollectionId,
int? MediaItemId,
int? MultiCollectionId,
int? SmartCollectionId) : DeadAirFallbackResult;
} }
+15 -1
View File
@@ -8,9 +8,23 @@ public class Deco
public string Name { get; set; } public string Name { get; set; }
// watermark
public DecoMode WatermarkMode { get; set; }
public int? WatermarkId { get; set; } public int? WatermarkId { get; set; }
public ChannelWatermark Watermark { get; set; } public ChannelWatermark Watermark { get; set; }
// can be added directly to playouts // dead air fallback
public DecoMode DeadAirFallbackMode { get; set; }
public ProgramScheduleItemCollectionType DeadAirFallbackCollectionType { get; set; }
public int? DeadAirFallbackCollectionId { get; set; }
public Collection DeadAirFallbackCollection { get; set; }
public int? DeadAirFallbackMediaItemId { get; set; }
public MediaItem DeadAirFallbackMediaItem { get; set; }
public int? DeadAirFallbackMultiCollectionId { get; set; }
public MultiCollection DeadAirFallbackMultiCollection { get; set; }
public int? DeadAirFallbackSmartCollectionId { get; set; }
public SmartCollection DeadAirFallbackSmartCollection { get; set; }
// can be added directly to (block) playouts
public ICollection<Playout> Playouts { get; set; } public ICollection<Playout> Playouts { get; set; }
} }
@@ -0,0 +1,8 @@
namespace ErsatzTV.Core.Domain.Scheduling;
public enum DecoMode
{
Inherit = 0,
Disable = 1,
Override = 2
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,175 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Add_DecoMode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "DeadAirFallbackCollectionId",
table: "Deco",
type: "int",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "DeadAirFallbackCollectionType",
table: "Deco",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "DeadAirFallbackMediaItemId",
table: "Deco",
type: "int",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "DeadAirFallbackMode",
table: "Deco",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "DeadAirFallbackMultiCollectionId",
table: "Deco",
type: "int",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "DeadAirFallbackSmartCollectionId",
table: "Deco",
type: "int",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "WatermarkMode",
table: "Deco",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateIndex(
name: "IX_Deco_DeadAirFallbackCollectionId",
table: "Deco",
column: "DeadAirFallbackCollectionId");
migrationBuilder.CreateIndex(
name: "IX_Deco_DeadAirFallbackMediaItemId",
table: "Deco",
column: "DeadAirFallbackMediaItemId");
migrationBuilder.CreateIndex(
name: "IX_Deco_DeadAirFallbackMultiCollectionId",
table: "Deco",
column: "DeadAirFallbackMultiCollectionId");
migrationBuilder.CreateIndex(
name: "IX_Deco_DeadAirFallbackSmartCollectionId",
table: "Deco",
column: "DeadAirFallbackSmartCollectionId");
migrationBuilder.AddForeignKey(
name: "FK_Deco_Collection_DeadAirFallbackCollectionId",
table: "Deco",
column: "DeadAirFallbackCollectionId",
principalTable: "Collection",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Deco_MediaItem_DeadAirFallbackMediaItemId",
table: "Deco",
column: "DeadAirFallbackMediaItemId",
principalTable: "MediaItem",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Deco_MultiCollection_DeadAirFallbackMultiCollectionId",
table: "Deco",
column: "DeadAirFallbackMultiCollectionId",
principalTable: "MultiCollection",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Deco_SmartCollection_DeadAirFallbackSmartCollectionId",
table: "Deco",
column: "DeadAirFallbackSmartCollectionId",
principalTable: "SmartCollection",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Deco_Collection_DeadAirFallbackCollectionId",
table: "Deco");
migrationBuilder.DropForeignKey(
name: "FK_Deco_MediaItem_DeadAirFallbackMediaItemId",
table: "Deco");
migrationBuilder.DropForeignKey(
name: "FK_Deco_MultiCollection_DeadAirFallbackMultiCollectionId",
table: "Deco");
migrationBuilder.DropForeignKey(
name: "FK_Deco_SmartCollection_DeadAirFallbackSmartCollectionId",
table: "Deco");
migrationBuilder.DropIndex(
name: "IX_Deco_DeadAirFallbackCollectionId",
table: "Deco");
migrationBuilder.DropIndex(
name: "IX_Deco_DeadAirFallbackMediaItemId",
table: "Deco");
migrationBuilder.DropIndex(
name: "IX_Deco_DeadAirFallbackMultiCollectionId",
table: "Deco");
migrationBuilder.DropIndex(
name: "IX_Deco_DeadAirFallbackSmartCollectionId",
table: "Deco");
migrationBuilder.DropColumn(
name: "DeadAirFallbackCollectionId",
table: "Deco");
migrationBuilder.DropColumn(
name: "DeadAirFallbackCollectionType",
table: "Deco");
migrationBuilder.DropColumn(
name: "DeadAirFallbackMediaItemId",
table: "Deco");
migrationBuilder.DropColumn(
name: "DeadAirFallbackMode",
table: "Deco");
migrationBuilder.DropColumn(
name: "DeadAirFallbackMultiCollectionId",
table: "Deco");
migrationBuilder.DropColumn(
name: "DeadAirFallbackSmartCollectionId",
table: "Deco");
migrationBuilder.DropColumn(
name: "WatermarkMode",
table: "Deco");
}
}
}
@@ -2145,6 +2145,24 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id")); MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int?>("DeadAirFallbackCollectionId")
.HasColumnType("int");
b.Property<int>("DeadAirFallbackCollectionType")
.HasColumnType("int");
b.Property<int?>("DeadAirFallbackMediaItemId")
.HasColumnType("int");
b.Property<int>("DeadAirFallbackMode")
.HasColumnType("int");
b.Property<int?>("DeadAirFallbackMultiCollectionId")
.HasColumnType("int");
b.Property<int?>("DeadAirFallbackSmartCollectionId")
.HasColumnType("int");
b.Property<int>("DecoGroupId") b.Property<int>("DecoGroupId")
.HasColumnType("int"); .HasColumnType("int");
@@ -2154,8 +2172,19 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<int?>("WatermarkId") b.Property<int?>("WatermarkId")
.HasColumnType("int"); .HasColumnType("int");
b.Property<int>("WatermarkMode")
.HasColumnType("int");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("DeadAirFallbackCollectionId");
b.HasIndex("DeadAirFallbackMediaItemId");
b.HasIndex("DeadAirFallbackMultiCollectionId");
b.HasIndex("DeadAirFallbackSmartCollectionId");
b.HasIndex("WatermarkId"); b.HasIndex("WatermarkId");
b.HasIndex("DecoGroupId", "Name") b.HasIndex("DecoGroupId", "Name")
@@ -4409,6 +4438,26 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.Deco", b => modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.Deco", b =>
{ {
b.HasOne("ErsatzTV.Core.Domain.Collection", "DeadAirFallbackCollection")
.WithMany()
.HasForeignKey("DeadAirFallbackCollectionId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("ErsatzTV.Core.Domain.MediaItem", "DeadAirFallbackMediaItem")
.WithMany()
.HasForeignKey("DeadAirFallbackMediaItemId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("ErsatzTV.Core.Domain.MultiCollection", "DeadAirFallbackMultiCollection")
.WithMany()
.HasForeignKey("DeadAirFallbackMultiCollectionId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("ErsatzTV.Core.Domain.SmartCollection", "DeadAirFallbackSmartCollection")
.WithMany()
.HasForeignKey("DeadAirFallbackSmartCollectionId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("ErsatzTV.Core.Domain.Scheduling.DecoGroup", "DecoGroup") b.HasOne("ErsatzTV.Core.Domain.Scheduling.DecoGroup", "DecoGroup")
.WithMany("Decos") .WithMany("Decos")
.HasForeignKey("DecoGroupId") .HasForeignKey("DecoGroupId")
@@ -4420,6 +4469,14 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
.HasForeignKey("WatermarkId") .HasForeignKey("WatermarkId")
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
b.Navigation("DeadAirFallbackCollection");
b.Navigation("DeadAirFallbackMediaItem");
b.Navigation("DeadAirFallbackMultiCollection");
b.Navigation("DeadAirFallbackSmartCollection");
b.Navigation("DecoGroup"); b.Navigation("DecoGroup");
b.Navigation("Watermark"); b.Navigation("Watermark");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,153 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Add_Deco_DeadAirFallback : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "DeadAirFallbackCollectionId",
table: "Deco",
type: "INTEGER",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "DeadAirFallbackCollectionType",
table: "Deco",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "DeadAirFallbackMediaItemId",
table: "Deco",
type: "INTEGER",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "DeadAirFallbackMultiCollectionId",
table: "Deco",
type: "INTEGER",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "DeadAirFallbackSmartCollectionId",
table: "Deco",
type: "INTEGER",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_Deco_DeadAirFallbackCollectionId",
table: "Deco",
column: "DeadAirFallbackCollectionId");
migrationBuilder.CreateIndex(
name: "IX_Deco_DeadAirFallbackMediaItemId",
table: "Deco",
column: "DeadAirFallbackMediaItemId");
migrationBuilder.CreateIndex(
name: "IX_Deco_DeadAirFallbackMultiCollectionId",
table: "Deco",
column: "DeadAirFallbackMultiCollectionId");
migrationBuilder.CreateIndex(
name: "IX_Deco_DeadAirFallbackSmartCollectionId",
table: "Deco",
column: "DeadAirFallbackSmartCollectionId");
migrationBuilder.AddForeignKey(
name: "FK_Deco_Collection_DeadAirFallbackCollectionId",
table: "Deco",
column: "DeadAirFallbackCollectionId",
principalTable: "Collection",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Deco_MediaItem_DeadAirFallbackMediaItemId",
table: "Deco",
column: "DeadAirFallbackMediaItemId",
principalTable: "MediaItem",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Deco_MultiCollection_DeadAirFallbackMultiCollectionId",
table: "Deco",
column: "DeadAirFallbackMultiCollectionId",
principalTable: "MultiCollection",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Deco_SmartCollection_DeadAirFallbackSmartCollectionId",
table: "Deco",
column: "DeadAirFallbackSmartCollectionId",
principalTable: "SmartCollection",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Deco_Collection_DeadAirFallbackCollectionId",
table: "Deco");
migrationBuilder.DropForeignKey(
name: "FK_Deco_MediaItem_DeadAirFallbackMediaItemId",
table: "Deco");
migrationBuilder.DropForeignKey(
name: "FK_Deco_MultiCollection_DeadAirFallbackMultiCollectionId",
table: "Deco");
migrationBuilder.DropForeignKey(
name: "FK_Deco_SmartCollection_DeadAirFallbackSmartCollectionId",
table: "Deco");
migrationBuilder.DropIndex(
name: "IX_Deco_DeadAirFallbackCollectionId",
table: "Deco");
migrationBuilder.DropIndex(
name: "IX_Deco_DeadAirFallbackMediaItemId",
table: "Deco");
migrationBuilder.DropIndex(
name: "IX_Deco_DeadAirFallbackMultiCollectionId",
table: "Deco");
migrationBuilder.DropIndex(
name: "IX_Deco_DeadAirFallbackSmartCollectionId",
table: "Deco");
migrationBuilder.DropColumn(
name: "DeadAirFallbackCollectionId",
table: "Deco");
migrationBuilder.DropColumn(
name: "DeadAirFallbackCollectionType",
table: "Deco");
migrationBuilder.DropColumn(
name: "DeadAirFallbackMediaItemId",
table: "Deco");
migrationBuilder.DropColumn(
name: "DeadAirFallbackMultiCollectionId",
table: "Deco");
migrationBuilder.DropColumn(
name: "DeadAirFallbackSmartCollectionId",
table: "Deco");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Add_DecoMode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "DeadAirFallbackMode",
table: "Deco",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "WatermarkMode",
table: "Deco",
type: "INTEGER",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "DeadAirFallbackMode",
table: "Deco");
migrationBuilder.DropColumn(
name: "WatermarkMode",
table: "Deco");
}
}
}
@@ -2036,6 +2036,24 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<int?>("DeadAirFallbackCollectionId")
.HasColumnType("INTEGER");
b.Property<int>("DeadAirFallbackCollectionType")
.HasColumnType("INTEGER");
b.Property<int?>("DeadAirFallbackMediaItemId")
.HasColumnType("INTEGER");
b.Property<int>("DeadAirFallbackMode")
.HasColumnType("INTEGER");
b.Property<int?>("DeadAirFallbackMultiCollectionId")
.HasColumnType("INTEGER");
b.Property<int?>("DeadAirFallbackSmartCollectionId")
.HasColumnType("INTEGER");
b.Property<int>("DecoGroupId") b.Property<int>("DecoGroupId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@@ -2045,8 +2063,19 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<int?>("WatermarkId") b.Property<int?>("WatermarkId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<int>("WatermarkMode")
.HasColumnType("INTEGER");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("DeadAirFallbackCollectionId");
b.HasIndex("DeadAirFallbackMediaItemId");
b.HasIndex("DeadAirFallbackMultiCollectionId");
b.HasIndex("DeadAirFallbackSmartCollectionId");
b.HasIndex("WatermarkId"); b.HasIndex("WatermarkId");
b.HasIndex("DecoGroupId", "Name") b.HasIndex("DecoGroupId", "Name")
@@ -4254,6 +4283,26 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.Deco", b => modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.Deco", b =>
{ {
b.HasOne("ErsatzTV.Core.Domain.Collection", "DeadAirFallbackCollection")
.WithMany()
.HasForeignKey("DeadAirFallbackCollectionId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("ErsatzTV.Core.Domain.MediaItem", "DeadAirFallbackMediaItem")
.WithMany()
.HasForeignKey("DeadAirFallbackMediaItemId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("ErsatzTV.Core.Domain.MultiCollection", "DeadAirFallbackMultiCollection")
.WithMany()
.HasForeignKey("DeadAirFallbackMultiCollectionId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("ErsatzTV.Core.Domain.SmartCollection", "DeadAirFallbackSmartCollection")
.WithMany()
.HasForeignKey("DeadAirFallbackSmartCollectionId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("ErsatzTV.Core.Domain.Scheduling.DecoGroup", "DecoGroup") b.HasOne("ErsatzTV.Core.Domain.Scheduling.DecoGroup", "DecoGroup")
.WithMany("Decos") .WithMany("Decos")
.HasForeignKey("DecoGroupId") .HasForeignKey("DecoGroupId")
@@ -4265,6 +4314,14 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
.HasForeignKey("WatermarkId") .HasForeignKey("WatermarkId")
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
b.Navigation("DeadAirFallbackCollection");
b.Navigation("DeadAirFallbackMediaItem");
b.Navigation("DeadAirFallbackMultiCollection");
b.Navigation("DeadAirFallbackSmartCollection");
b.Navigation("DecoGroup"); b.Navigation("DecoGroup");
b.Navigation("Watermark"); b.Navigation("Watermark");
@@ -24,5 +24,29 @@ public class DecoConfiguration : IEntityTypeConfiguration<Deco>
.HasForeignKey(d => d.WatermarkId) .HasForeignKey(d => d.WatermarkId)
.OnDelete(DeleteBehavior.SetNull) .OnDelete(DeleteBehavior.SetNull)
.IsRequired(false); .IsRequired(false);
builder.HasOne(d => d.DeadAirFallbackCollection)
.WithMany()
.HasForeignKey(d => d.DeadAirFallbackCollectionId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired(false);
builder.HasOne(d => d.DeadAirFallbackMediaItem)
.WithMany()
.HasForeignKey(d => d.DeadAirFallbackMediaItemId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired(false);
builder.HasOne(d => d.DeadAirFallbackMultiCollection)
.WithMany()
.HasForeignKey(d => d.DeadAirFallbackMultiCollectionId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired(false);
builder.HasOne(d => d.DeadAirFallbackSmartCollection)
.WithMany()
.HasForeignKey(d => d.DeadAirFallbackSmartCollectionId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired(false);
} }
} }
+193 -11
View File
@@ -1,19 +1,43 @@
@page "/decos/{Id:int}" @page "/decos/{Id:int}"
@using ErsatzTV.Application.Scheduling @using ErsatzTV.Application.Scheduling
@using ErsatzTV.Application.Watermarks @using ErsatzTV.Application.Watermarks
@using ErsatzTV.Application.MediaCollections
@using ErsatzTV.Application.MediaItems
@using ErsatzTV.Application.Television
@using ErsatzTV.Application.Artists
@using ErsatzTV.Core.Domain.Scheduling
@implements IDisposable @implements IDisposable
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject ILogger<DecoEditor> Logger @inject ILogger<DecoEditor> Logger
@inject ISnackbar Snackbar @inject ISnackbar Snackbar
@inject IMediator Mediator @inject IMediator Mediator
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8"> <MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8" Style="display: flex; flex-direction: row">
<MudText Typo="Typo.h4" Class="mb-4">Edit Deco</MudText> <MudStack Class="mr-6">
<div style="max-width: 400px"> <MudText Typo="Typo.h4" Class="mb-4">Edit Deco</MudText>
<MudCard> <MudCard Class="mb-6" Style="width: 350px">
<MudCardContent> <MudCardContent>
<MudTextField Label="Name" @bind-Value="_deco.Name" For="@(() => _deco.Name)"/> <MudTextField Label="Name" @bind-Value="_deco.Name" For="@(() => _deco.Name)"/>
<MudSelect Class="mt-3" Label="Watermark" @bind-Value="_deco.WatermarkId" For="@(() => _deco.WatermarkId)" </MudCardContent>
<MudCardActions>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveChanges())" StartIcon="@Icons.Material.Filled.Save">
Save Changes
</MudButton>
</MudCardActions>
</MudCard>
<MudCard Class="mb-6" Style="width: 350px">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h6">Watermark</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudSelect Label="Watermark Mode" @bind-Value="_deco.WatermarkMode" For="@(() => _deco.WatermarkMode)">
<MudSelectItem Value="DecoMode.Inherit">Inherit</MudSelectItem>
<MudSelectItem Value="DecoMode.Disable">Disable</MudSelectItem>
<MudSelectItem Value="DecoMode.Override">Override</MudSelectItem>
</MudSelect>
<MudSelect Disabled="@(_deco.WatermarkMode != DecoMode.Override)" Label="Watermark Override" @bind-Value="_deco.WatermarkId" For="@(() => _deco.WatermarkId)"
Clearable="true"> Clearable="true">
<MudSelectItem T="int?" Value="@((int?)null)">(none)</MudSelectItem> <MudSelectItem T="int?" Value="@((int?)null)">(none)</MudSelectItem>
@foreach (WatermarkViewModel watermark in _watermarks) @foreach (WatermarkViewModel watermark in _watermarks)
@@ -23,10 +47,116 @@
</MudSelect> </MudSelect>
</MudCardContent> </MudCardContent>
</MudCard> </MudCard>
</div> <MudCard Class="mb-6" Style="width: 350px">
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveChanges())" Class="mt-4 ml-4"> <MudCardHeader>
Save Changes <CardHeaderContent>
</MudButton> <MudTooltip Style="max-width: 350px">
<ChildContent>
<MudText Typo="Typo.h6">Dead Air Fallback</MudText>
</ChildContent>
<TooltipContent>
<MudText Typo="Typo.body2">When no playout item is found for the current time, *one* item will be randomly selected from this collection and looped and cut to exactly fit until the start of the next playout item.</MudText>
<MudText Typo="Typo.body2" Class="mt-3">This replaces the "Channel is Offline" image that would otherwise display.</MudText>
</TooltipContent>
</MudTooltip>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudSelect Label="Dead Air Fallback Mode" @bind-Value="_deco.DeadAirFallbackMode" For="@(() => _deco.DeadAirFallbackMode)">
<MudSelectItem Value="DecoMode.Inherit">Inherit</MudSelectItem>
<MudSelectItem Value="DecoMode.Disable">Disable</MudSelectItem>
<MudSelectItem Value="DecoMode.Override">Override</MudSelectItem>
</MudSelect>
<MudSelect Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Dead Air Fallback Collection Type"
@bind-Value="_deco.DeadAirFallbackCollectionType"
For="@(() => _deco.DeadAirFallbackCollectionType)">
@foreach (ProgramScheduleItemCollectionType collectionType in Enum.GetValues<ProgramScheduleItemCollectionType>())
{
<MudSelectItem Value="@collectionType">@collectionType</MudSelectItem>
}
</MudSelect>
@if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.Collection)
{
<MudSelect Class="mt-3"
T="MediaCollectionViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Collection"
@bind-value="_deco.DeadAirFallbackCollection">
@foreach (MediaCollectionViewModel collection in _mediaCollections)
{
<MudSelectItem Value="@collection">@collection.Name</MudSelectItem>
}
</MudSelect>
}
@if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.MultiCollection)
{
<MudSelect Class="mt-3"
T="MultiCollectionViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Multi Collection"
@bind-value="_deco.DeadAirFallbackMultiCollection">
@foreach (MultiCollectionViewModel collection in _multiCollections)
{
<MudSelectItem Value="@collection">@collection.Name</MudSelectItem>
}
</MudSelect>
}
@if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.SmartCollection)
{
<MudSelect Class="mt-3"
T="SmartCollectionViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Smart Collection"
@bind-value="_deco.DeadAirFallbackSmartCollection">
@foreach (SmartCollectionViewModel collection in _smartCollections)
{
<MudSelectItem Value="@collection">@collection.Name</MudSelectItem>
}
</MudSelect>
}
@if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.TelevisionShow)
{
<MudSelect Class="mt-3"
T="NamedMediaItemViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Television Show"
@bind-value="_deco.DeadAirFallbackMediaItem">
@foreach (NamedMediaItemViewModel show in _televisionShows)
{
<MudSelectItem Value="@show">@show.Name</MudSelectItem>
}
</MudSelect>
}
@if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.TelevisionSeason)
{
<MudSelect Class="mt-3"
T="NamedMediaItemViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Television Season"
@bind-value="_deco.DeadAirFallbackMediaItem">
@foreach (NamedMediaItemViewModel season in _televisionSeasons)
{
<MudSelectItem Value="@season">@season.Name</MudSelectItem>
}
</MudSelect>
}
@if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.Artist)
{
<MudSelect Class="mt-3"
T="NamedMediaItemViewModel"
Disabled="@(_deco.DeadAirFallbackMode != DecoMode.Override)"
Label="Artist"
@bind-value="_deco.DeadAirFallbackMediaItem">
@foreach (NamedMediaItemViewModel artist in _artists)
{
<MudSelectItem Value="@artist">@artist.Name</MudSelectItem>
}
</MudSelect>
}
</MudCardContent>
</MudCard>
</MudStack>
</MudContainer> </MudContainer>
@code { @code {
@@ -36,8 +166,17 @@
public int Id { get; set; } public int Id { get; set; }
private DecoEditViewModel _deco = new(); private DecoEditViewModel _deco = new();
private List<WatermarkViewModel> _watermarks = []; private List<WatermarkViewModel> _watermarks = [];
private List<MediaCollectionViewModel> _mediaCollections = [];
private List<MultiCollectionViewModel> _multiCollections = [];
private List<SmartCollectionViewModel> _smartCollections = [];
private List<NamedMediaItemViewModel> _televisionShows = [];
private List<NamedMediaItemViewModel> _televisionSeasons = [];
private List<NamedMediaItemViewModel> _artists = [];
public void Dispose() public void Dispose()
{ {
_cts.Cancel(); _cts.Cancel();
@@ -47,6 +186,20 @@
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
await LoadWatermarks(); await LoadWatermarks();
_mediaCollections = await Mediator.Send(new GetAllCollections(), _cts.Token)
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
_multiCollections = await Mediator.Send(new GetAllMultiCollections(), _cts.Token)
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
_smartCollections = await Mediator.Send(new GetAllSmartCollections(), _cts.Token)
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
_televisionShows = await Mediator.Send(new GetAllTelevisionShows(), _cts.Token)
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
_televisionSeasons = await Mediator.Send(new GetAllTelevisionSeasons(), _cts.Token)
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
_artists = await Mediator.Send(new GetAllArtists(), _cts.Token)
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
await LoadDeco(); await LoadDeco();
} }
@@ -68,15 +221,44 @@
{ {
Name = deco.Name, Name = deco.Name,
DecoGroupId = deco.DecoGroupId, DecoGroupId = deco.DecoGroupId,
WatermarkId = deco.WatermarkId WatermarkMode = deco.WatermarkMode,
WatermarkId = deco.WatermarkId,
DeadAirFallbackMode = deco.DeadAirFallbackMode,
DeadAirFallbackCollectionType = deco.DeadAirFallbackCollectionType,
DeadAirFallbackCollection = deco.DeadAirFallbackCollectionId.HasValue
? _mediaCollections.Find(c => c.Id == deco.DeadAirFallbackCollectionId.Value)
: null,
DeadAirFallbackMediaItem = deco.DeadAirFallbackMediaItemId.HasValue
? _televisionShows.Append(_televisionSeasons).Append(_artists).ToList()
.Find(vm => vm.MediaItemId == deco.DeadAirFallbackMediaItemId.Value)
: null,
DeadAirFallbackMultiCollection = deco.DeadAirFallbackMultiCollectionId.HasValue
? _multiCollections.Find(c => c.Id == deco.DeadAirFallbackMultiCollectionId.Value)
: null,
DeadAirFallbackSmartCollection = deco.DeadAirFallbackSmartCollectionId.HasValue
? _smartCollections.Find(c => c.Id == deco.DeadAirFallbackSmartCollectionId.Value)
: null
}; };
} }
} }
private async Task SaveChanges() private async Task SaveChanges()
{ {
var request = new UpdateDeco(
Id,
_deco.DecoGroupId,
_deco.Name,
_deco.WatermarkMode,
_deco.WatermarkId,
_deco.DeadAirFallbackMode,
_deco.DeadAirFallbackCollectionType,
_deco.DeadAirFallbackCollection?.Id,
_deco.DeadAirFallbackMediaItem?.MediaItemId,
_deco.DeadAirFallbackMultiCollection?.Id,
_deco.DeadAirFallbackSmartCollection?.Id);
Seq<BaseError> errorMessages = await Mediator Seq<BaseError> errorMessages = await Mediator
.Send(new UpdateDeco(Id, _deco.DecoGroupId, _deco.Name, _deco.WatermarkId), _cts.Token) .Send(request, _cts.Token)
.Map(e => e.LeftToSeq()); .Map(e => e.LeftToSeq());
errorMessages.HeadOrNone().Match( errorMessages.HeadOrNone().Match(
+12
View File
@@ -1,8 +1,20 @@
using ErsatzTV.Application.MediaCollections;
using ErsatzTV.Application.MediaItems;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling;
namespace ErsatzTV.ViewModels; namespace ErsatzTV.ViewModels;
public class DecoEditViewModel public class DecoEditViewModel
{ {
public int DecoGroupId { get; set; } public int DecoGroupId { get; set; }
public string Name { get; set; } public string Name { get; set; }
public DecoMode WatermarkMode { get; set; }
public int? WatermarkId { get; set; } public int? WatermarkId { get; set; }
public DecoMode DeadAirFallbackMode { get; set; }
public ProgramScheduleItemCollectionType DeadAirFallbackCollectionType { get; set; }
public MediaCollectionViewModel DeadAirFallbackCollection { get; set; }
public MultiCollectionViewModel DeadAirFallbackMultiCollection { get; set; }
public SmartCollectionViewModel DeadAirFallbackSmartCollection { get; set; }
public NamedMediaItemViewModel DeadAirFallbackMediaItem { get; set; }
} }