Browse Source

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
pull/1676/head
Jason Dove 1 year ago committed by GitHub
parent
commit
e305222141
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 8
      CHANGELOG.md
  2. 16
      ErsatzTV.Application/Scheduling/Commands/UpdateDeco.cs
  3. 21
      ErsatzTV.Application/Scheduling/Commands/UpdateDecoHandler.cs
  4. 16
      ErsatzTV.Application/Scheduling/DecoViewModel.cs
  5. 13
      ErsatzTV.Application/Scheduling/Mapper.cs
  6. 268
      ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs
  7. 16
      ErsatzTV.Core/Domain/Scheduling/Deco.cs
  8. 8
      ErsatzTV.Core/Domain/Scheduling/DecoMode.cs
  9. 5590
      ErsatzTV.Infrastructure.MySql/Migrations/20240406013240_Add_DecoMode.Designer.cs
  10. 175
      ErsatzTV.Infrastructure.MySql/Migrations/20240406013240_Add_DecoMode.cs
  11. 57
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  12. 5429
      ErsatzTV.Infrastructure.Sqlite/Migrations/20240405031137_Add_Deco_DeadAirFallback.Designer.cs
  13. 153
      ErsatzTV.Infrastructure.Sqlite/Migrations/20240405031137_Add_Deco_DeadAirFallback.cs
  14. 5435
      ErsatzTV.Infrastructure.Sqlite/Migrations/20240405205211_Add_DecoMode.Designer.cs
  15. 40
      ErsatzTV.Infrastructure.Sqlite/Migrations/20240405205211_Add_DecoMode.cs
  16. 57
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  17. 24
      ErsatzTV.Infrastructure/Data/Configurations/Scheduling/DecoConfiguration.cs
  18. 202
      ErsatzTV/Pages/DecoEditor.razor
  19. 12
      ErsatzTV/ViewModels/DecoEditViewModel.cs

8
CHANGELOG.md

@ -9,14 +9,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -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 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
- 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 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
- 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
- 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
- Fix some cases of 404s from Plex when files were replaced and scanning the library from ETV didn't help

16
ErsatzTV.Application/Scheduling/Commands/UpdateDeco.cs

@ -1,5 +1,19 @@ @@ -1,5 +1,19 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.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>>;

21
ErsatzTV.Application/Scheduling/Commands/UpdateDecoHandler.cs

@ -22,7 +22,26 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory) @@ -22,7 +22,26 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory)
UpdateDeco request)
{
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();

16
ErsatzTV.Application/Scheduling/DecoViewModel.cs

@ -1,3 +1,17 @@ @@ -1,3 +1,17 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.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);

13
ErsatzTV.Application/Scheduling/Mapper.cs

@ -50,7 +50,18 @@ internal static class Mapper @@ -50,7 +50,18 @@ internal static class Mapper
new(decoGroup.Id, decoGroup.Name, decoGroup.Decos.Count);
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) =>
new(decoTemplateGroup.Id, decoTemplateGroup.Name, decoTemplateGroup.DecoTemplates.Count);

268
ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs

@ -79,9 +79,19 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -79,9 +79,19 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
DateTimeOffset now = request.Now;
Either<BaseError, PlayoutItemWithPath> maybePlayoutItem = await dbContext.PlayoutItems
// get playout deco
.Include(i => i.Playout)
.ThenInclude(p => p.Deco)
.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)
.ThenInclude(mi => (mi as Episode).EpisodeMetadata)
.ThenInclude(em => em.Subtitles)
@ -163,7 +173,31 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -163,7 +173,31 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
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())
@ -198,62 +232,20 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -198,62 +232,20 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
watermarkId => dbContext.ChannelWatermarks
.SelectOneAsync(w => w.Id, w => w.Id == watermarkId));
Option<ChannelWatermark> playoutItemWatermark = Optional(playoutItemWithPath.PlayoutItem.Watermark);
if (playoutItemWatermark.IsNone &&
playoutItemWithPath.PlayoutItem.Playout.ProgramSchedulePlayoutType is ProgramSchedulePlayoutType.Block)
{
_logger.LogDebug("Block playout item has no watermark; checking for deco");
// check for playout template deco
// load all playout templates
// get playout template for start time
// check for deco
// load all templates that have decos
List<PlayoutTemplate> playoutTemplates = await dbContext.PlayoutTemplates
.AsNoTracking()
.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))
Option<ChannelWatermark> playoutItemWatermark = Option<ChannelWatermark>.None;
bool disableWatermarks = playoutItemWithPath.PlayoutItem.DisableWatermarks;
WatermarkResult watermarkResult = GetPlayoutItemWatermark(playoutItemWithPath.PlayoutItem.Playout, now);
switch (watermarkResult)
{
_logger.LogDebug(
"Block playout has default deco with watermark; will use for this playout item");
playoutItemWatermark = watermark;
}
}
}
case InheritWatermark:
// do nothing, other code will fall back to channel/global
break;
case DisableWatermark:
disableWatermarks = true;
break;
case CustomWatermark watermark:
playoutItemWatermark = watermark.Watermark;
break;
}
if (playoutItemWithPath.PlayoutItem.MediaItem is Song song)
@ -312,7 +304,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -312,7 +304,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
outPoint,
request.PtsOffset,
request.TargetFramerate,
playoutItemWithPath.PlayoutItem.DisableWatermarks,
disableWatermarks,
_ => { });
var result = new PlayoutItemProcessModel(process, duration, finish, true);
@ -463,10 +455,31 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -463,10 +455,31 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
private async Task<Either<BaseError, PlayoutItemWithPath>> CheckForFallbackFiller(
TvContext dbContext,
Channel channel,
Playout playout,
DateTimeOffset now)
{
Option<FillerPreset> maybeFallback = Option<FillerPreset>.None;
DeadAirFallbackResult decoDeadAirFallback = GetDecoDeadAirFallback(playout, now);
switch (decoDeadAirFallback)
{
case CustomDeadAirFallback custom:
maybeFallback = new FillerPreset
{
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
Option<FillerPreset> maybeFallback = await dbContext.FillerPresets
maybeFallback = await dbContext.FillerPresets
.SelectOneAsync(w => w.Id, w => w.Id == channel.FallbackFillerId);
// then check for global fallback
@ -476,6 +489,9 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -476,6 +489,9 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
.GetValue<int>(ConfigElementKey.FFmpegGlobalFallbackFillerId)
.BindT(fillerId => dbContext.FillerPresets.SelectOneAsync(w => w.Id, w => w.Id == fillerId));
}
break;
}
foreach (FillerPreset fallbackPreset in maybeFallback)
{
@ -641,4 +657,140 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -641,4 +657,140 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
_ => 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;
}

16
ErsatzTV.Core/Domain/Scheduling/Deco.cs

@ -8,9 +8,23 @@ public class Deco @@ -8,9 +8,23 @@ public class Deco
public string Name { get; set; }
// watermark
public DecoMode WatermarkMode { get; set; }
public int? WatermarkId { 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; }
}

8
ErsatzTV.Core/Domain/Scheduling/DecoMode.cs

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
namespace ErsatzTV.Core.Domain.Scheduling;
public enum DecoMode
{
Inherit = 0,
Disable = 1,
Override = 2
}

5590
ErsatzTV.Infrastructure.MySql/Migrations/20240406013240_Add_DecoMode.Designer.cs generated

File diff suppressed because it is too large Load Diff

175
ErsatzTV.Infrastructure.MySql/Migrations/20240406013240_Add_DecoMode.cs

@ -0,0 +1,175 @@ @@ -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");
}
}
}

57
ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs

@ -2145,6 +2145,24 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -2145,6 +2145,24 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
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")
.HasColumnType("int");
@ -2154,8 +2172,19 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -2154,8 +2172,19 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<int?>("WatermarkId")
.HasColumnType("int");
b.Property<int>("WatermarkMode")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("DeadAirFallbackCollectionId");
b.HasIndex("DeadAirFallbackMediaItemId");
b.HasIndex("DeadAirFallbackMultiCollectionId");
b.HasIndex("DeadAirFallbackSmartCollectionId");
b.HasIndex("WatermarkId");
b.HasIndex("DecoGroupId", "Name")
@ -4409,6 +4438,26 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -4409,6 +4438,26 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
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")
.WithMany("Decos")
.HasForeignKey("DecoGroupId")
@ -4420,6 +4469,14 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -4420,6 +4469,14 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
.HasForeignKey("WatermarkId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("DeadAirFallbackCollection");
b.Navigation("DeadAirFallbackMediaItem");
b.Navigation("DeadAirFallbackMultiCollection");
b.Navigation("DeadAirFallbackSmartCollection");
b.Navigation("DecoGroup");
b.Navigation("Watermark");

5429
ErsatzTV.Infrastructure.Sqlite/Migrations/20240405031137_Add_Deco_DeadAirFallback.Designer.cs generated

File diff suppressed because it is too large Load Diff

153
ErsatzTV.Infrastructure.Sqlite/Migrations/20240405031137_Add_Deco_DeadAirFallback.cs

@ -0,0 +1,153 @@ @@ -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");
}
}
}

5435
ErsatzTV.Infrastructure.Sqlite/Migrations/20240405205211_Add_DecoMode.Designer.cs generated

File diff suppressed because it is too large Load Diff

40
ErsatzTV.Infrastructure.Sqlite/Migrations/20240405205211_Add_DecoMode.cs

@ -0,0 +1,40 @@ @@ -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");
}
}
}

57
ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs

@ -2036,6 +2036,24 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -2036,6 +2036,24 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
.ValueGeneratedOnAdd()
.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")
.HasColumnType("INTEGER");
@ -2045,8 +2063,19 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -2045,8 +2063,19 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<int?>("WatermarkId")
.HasColumnType("INTEGER");
b.Property<int>("WatermarkMode")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("DeadAirFallbackCollectionId");
b.HasIndex("DeadAirFallbackMediaItemId");
b.HasIndex("DeadAirFallbackMultiCollectionId");
b.HasIndex("DeadAirFallbackSmartCollectionId");
b.HasIndex("WatermarkId");
b.HasIndex("DecoGroupId", "Name")
@ -4254,6 +4283,26 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -4254,6 +4283,26 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
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")
.WithMany("Decos")
.HasForeignKey("DecoGroupId")
@ -4265,6 +4314,14 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -4265,6 +4314,14 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
.HasForeignKey("WatermarkId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("DeadAirFallbackCollection");
b.Navigation("DeadAirFallbackMediaItem");
b.Navigation("DeadAirFallbackMultiCollection");
b.Navigation("DeadAirFallbackSmartCollection");
b.Navigation("DecoGroup");
b.Navigation("Watermark");

24
ErsatzTV.Infrastructure/Data/Configurations/Scheduling/DecoConfiguration.cs

@ -24,5 +24,29 @@ public class DecoConfiguration : IEntityTypeConfiguration<Deco> @@ -24,5 +24,29 @@ public class DecoConfiguration : IEntityTypeConfiguration<Deco>
.HasForeignKey(d => d.WatermarkId)
.OnDelete(DeleteBehavior.SetNull)
.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);
}
}

202
ErsatzTV/Pages/DecoEditor.razor

@ -1,19 +1,43 @@ @@ -1,19 +1,43 @@
@page "/decos/{Id:int}"
@using ErsatzTV.Application.Scheduling
@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
@inject NavigationManager NavigationManager
@inject ILogger<DecoEditor> Logger
@inject ISnackbar Snackbar
@inject IMediator Mediator
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8" Style="display: flex; flex-direction: row">
<MudStack Class="mr-6">
<MudText Typo="Typo.h4" Class="mb-4">Edit Deco</MudText>
<div style="max-width: 400px">
<MudCard>
<MudCard Class="mb-6" Style="width: 350px">
<MudCardContent>
<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">
<MudSelectItem T="int?" Value="@((int?)null)">(none)</MudSelectItem>
@foreach (WatermarkViewModel watermark in _watermarks)
@ -23,10 +47,116 @@ @@ -23,10 +47,116 @@
</MudSelect>
</MudCardContent>
</MudCard>
</div>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveChanges())" Class="mt-4 ml-4">
Save Changes
</MudButton>
<MudCard Class="mb-6" Style="width: 350px">
<MudCardHeader>
<CardHeaderContent>
<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>
@code {
@ -36,8 +166,17 @@ @@ -36,8 +166,17 @@
public int Id { get; set; }
private DecoEditViewModel _deco = new();
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()
{
_cts.Cancel();
@ -47,6 +186,20 @@ @@ -47,6 +186,20 @@
protected override async Task OnParametersSetAsync()
{
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();
}
@ -68,15 +221,44 @@ @@ -68,15 +221,44 @@
{
Name = deco.Name,
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()
{
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
.Send(new UpdateDeco(Id, _deco.DecoGroupId, _deco.Name, _deco.WatermarkId), _cts.Token)
.Send(request, _cts.Token)
.Map(e => e.LeftToSeq());
errorMessages.HeadOrNone().Match(

12
ErsatzTV/ViewModels/DecoEditViewModel.cs

@ -1,8 +1,20 @@ @@ -1,8 +1,20 @@
using ErsatzTV.Application.MediaCollections;
using ErsatzTV.Application.MediaItems;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling;
namespace ErsatzTV.ViewModels;
public class DecoEditViewModel
{
public int DecoGroupId { get; set; }
public string Name { get; set; }
public DecoMode WatermarkMode { 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; }
}

Loading…
Cancel
Save