Browse Source

add watermarks and graphics elements to block items (#2424)

pull/2425/head
Jason Dove 4 months ago committed by GitHub
parent
commit
9aa7c44388
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 6
      ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs
  3. 6
      ErsatzTV.Application/Scheduling/BlockItemViewModel.cs
  4. 4
      ErsatzTV.Application/Scheduling/Commands/ReplaceBlockItem.cs
  5. 39
      ErsatzTV.Application/Scheduling/Commands/ReplaceBlockItemsHandler.cs
  6. 4
      ErsatzTV.Application/Scheduling/Mapper.cs
  7. 4
      ErsatzTV.Application/Scheduling/Queries/GetBlockItemsHandler.cs
  8. 11
      ErsatzTV.Core/Domain/BlockItemGraphicsElement.cs
  9. 11
      ErsatzTV.Core/Domain/BlockItemWatermark.cs
  10. 2
      ErsatzTV.Core/Domain/ChannelWatermark.cs
  11. 2
      ErsatzTV.Core/Domain/GraphicsElement.cs
  12. 4
      ErsatzTV.Core/Domain/Scheduling/BlockItem.cs
  13. 25
      ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutBuilder.cs
  14. 6720
      ErsatzTV.Infrastructure.MySql/Migrations/20250916020821_Add_BlockItemGraphicsElements.Designer.cs
  15. 84
      ErsatzTV.Infrastructure.MySql/Migrations/20250916020821_Add_BlockItemGraphicsElements.cs
  16. 79
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  17. 6549
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250916020921_Add_BlockItemGraphicsElements.Designer.cs
  18. 82
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250916020921_Add_BlockItemGraphicsElements.cs
  19. 79
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  20. 27
      ErsatzTV.Infrastructure/Data/Configurations/Scheduling/BlockItemConfiguration.cs
  21. 56
      ErsatzTV/Pages/BlockEditor.razor
  22. 6
      ErsatzTV/ViewModels/BlockItemEditViewModel.cs

2
CHANGELOG.md

@ -18,6 +18,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -18,6 +18,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Otherwise, the rerun collection would be considered "empty" which prevents the playout build altogether
- Add `Rkmpp` hardware acceleration by @peterdey
- This is supported using jellyfin-ffmpeg7 on devices like Orange Pi 5 Plus and NanoPi R6S
- Block schedules: allow selecting multiple watermarks on block items
- Block schedules: allow selecting multiple graphics elements on block items
### Fixed
- Fix green output when libplacebo tonemapping is used with NVIDIA acceleration and 10-bit output in FFmpeg Profile

6
ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs

@ -405,6 +405,12 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -405,6 +405,12 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
.ThenInclude(t => t.Items)
.ThenInclude(i => i.Block)
.ThenInclude(b => b.Items)
.ThenInclude(i => i.BlockItemWatermarks)
.Include(t => t.Template)
.ThenInclude(t => t.Items)
.ThenInclude(i => i.Block)
.ThenInclude(b => b.Items)
.ThenInclude(i => i.BlockItemGraphicsElements)
.Include(t => t.DecoTemplate)
.ThenInclude(t => t.Items)
.ThenInclude(i => i.Deco)

6
ErsatzTV.Application/Scheduling/BlockItemViewModel.cs

@ -1,5 +1,7 @@ @@ -1,5 +1,7 @@
using ErsatzTV.Application.Graphics;
using ErsatzTV.Application.MediaCollections;
using ErsatzTV.Application.MediaItems;
using ErsatzTV.Application.Watermarks;
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.Scheduling;
@ -14,4 +16,6 @@ public record BlockItemViewModel( @@ -14,4 +16,6 @@ public record BlockItemViewModel(
NamedMediaItemViewModel MediaItem,
PlaybackOrder PlaybackOrder,
bool IncludeInProgramGuide,
bool DisableWatermarks);
bool DisableWatermarks,
List<WatermarkViewModel> Watermarks,
List<GraphicsElementViewModel> GraphicsElements);

4
ErsatzTV.Application/Scheduling/Commands/ReplaceBlockItem.cs

@ -11,4 +11,6 @@ public record ReplaceBlockItem( @@ -11,4 +11,6 @@ public record ReplaceBlockItem(
int? MediaItemId,
PlaybackOrder PlaybackOrder,
bool IncludeInProgramGuide,
bool DisableWatermarks);
bool DisableWatermarks,
List<int> WatermarkIds,
List<int> GraphicsElementIds);

39
ErsatzTV.Application/Scheduling/Commands/ReplaceBlockItemsHandler.cs

@ -44,8 +44,9 @@ public class ReplaceBlockItemsHandler(IDbContextFactory<TvContext> dbContextFact @@ -44,8 +44,9 @@ public class ReplaceBlockItemsHandler(IDbContextFactory<TvContext> dbContextFact
return block.Items.Map(Mapper.ProjectToViewModel).ToList();
}
private static BlockItem BuildItem(Block block, int index, ReplaceBlockItem item) =>
new()
private static BlockItem BuildItem(Block block, int index, ReplaceBlockItem item)
{
var result = new BlockItem
{
BlockId = block.Id,
Index = index,
@ -56,9 +57,36 @@ public class ReplaceBlockItemsHandler(IDbContextFactory<TvContext> dbContextFact @@ -56,9 +57,36 @@ public class ReplaceBlockItemsHandler(IDbContextFactory<TvContext> dbContextFact
MediaItemId = item.MediaItemId,
PlaybackOrder = item.PlaybackOrder,
IncludeInProgramGuide = item.IncludeInProgramGuide,
DisableWatermarks = item.DisableWatermarks
DisableWatermarks = item.DisableWatermarks,
BlockItemWatermarks = [],
BlockItemGraphicsElements = []
};
foreach (int watermarkId in item.WatermarkIds)
{
result.BlockItemWatermarks ??= [];
result.BlockItemWatermarks.Add(
new BlockItemWatermark
{
BlockItem = result,
WatermarkId = watermarkId
});
}
foreach (int graphicsElementId in item.GraphicsElementIds)
{
result.BlockItemGraphicsElements ??= [];
result.BlockItemGraphicsElements.Add(
new BlockItemGraphicsElement
{
BlockItem = result,
GraphicsElementId = graphicsElementId
});
}
return result;
}
private static Task<Validation<BaseError, Block>> Validate(
TvContext dbContext,
ReplaceBlockItems request,
@ -74,6 +102,11 @@ public class ReplaceBlockItemsHandler(IDbContextFactory<TvContext> dbContextFact @@ -74,6 +102,11 @@ public class ReplaceBlockItemsHandler(IDbContextFactory<TvContext> dbContextFact
CancellationToken cancellationToken) =>
dbContext.Blocks
.Include(b => b.Items)
.ThenInclude(i => i.BlockItemWatermarks)
.ThenInclude(wm => wm.Watermark)
.Include(b => b.Items)
.ThenInclude(i => i.BlockItemGraphicsElements)
.ThenInclude(ge => ge.GraphicsElement)
.SelectOneAsync(b => b.Id, b => b.Id == blockId, cancellationToken)
.Map(o => o.ToValidation<BaseError>("[BlockId] does not exist."));

4
ErsatzTV.Application/Scheduling/Mapper.cs

@ -64,7 +64,9 @@ internal static class Mapper @@ -64,7 +64,9 @@ internal static class Mapper
},
blockItem.PlaybackOrder,
blockItem.IncludeInProgramGuide,
blockItem.DisableWatermarks);
blockItem.DisableWatermarks,
blockItem.BlockItemWatermarks.Map(wm => Watermarks.Mapper.ProjectToViewModel(wm.Watermark)).ToList(),
blockItem.BlockItemGraphicsElements.Map(ge => Graphics.Mapper.ProjectToViewModel(ge.GraphicsElement)).ToList());
internal static TemplateGroupViewModel ProjectToViewModel(TemplateGroup templateGroup) =>
new(templateGroup.Id, templateGroup.Name, templateGroup.Templates.Count);

4
ErsatzTV.Application/Scheduling/Queries/GetBlockItemsHandler.cs

@ -31,6 +31,10 @@ public class GetBlockItemsHandler(IDbContextFactory<TvContext> dbContextFactory) @@ -31,6 +31,10 @@ public class GetBlockItemsHandler(IDbContextFactory<TvContext> dbContextFactory)
.Include(i => i.MediaItem)
.ThenInclude(i => (i as Artist).ArtistMetadata)
.ThenInclude(am => am.Artwork)
.Include(i => i.BlockItemWatermarks)
.ThenInclude(i => i.Watermark)
.Include(i => i.BlockItemGraphicsElements)
.ThenInclude(i => i.GraphicsElement)
.ToListAsync(cancellationToken);
if (allItems.All(bi => !bi.IncludeInProgramGuide))

11
ErsatzTV.Core/Domain/BlockItemGraphicsElement.cs

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
using ErsatzTV.Core.Domain.Scheduling;
namespace ErsatzTV.Core.Domain;
public class BlockItemGraphicsElement
{
public int BlockItemId { get; set; }
public BlockItem BlockItem { get; set; }
public int GraphicsElementId { get; set; }
public GraphicsElement GraphicsElement { get; set; }
}

11
ErsatzTV.Core/Domain/BlockItemWatermark.cs

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
using ErsatzTV.Core.Domain.Scheduling;
namespace ErsatzTV.Core.Domain;
public class BlockItemWatermark
{
public int BlockItemId { get; set; }
public BlockItem BlockItem { get; set; }
public int WatermarkId { get; set; }
public ChannelWatermark Watermark { get; set; }
}

2
ErsatzTV.Core/Domain/ChannelWatermark.cs

@ -25,6 +25,8 @@ public class ChannelWatermark @@ -25,6 +25,8 @@ public class ChannelWatermark
public List<PlayoutItemWatermark> PlayoutItemWatermarks { get; set; }
public List<ProgramScheduleItem> ProgramScheduleItems { get; set; }
public List<ProgramScheduleItemWatermark> ProgramScheduleItemWatermarks { get; set; }
public List<BlockItem> BlockItems { get; set; }
public List<BlockItemWatermark> BlockItemWatermarks { get; set; }
public List<Deco> Decos { get; set; }
public List<DecoWatermark> DecoWatermarks { get; set; }
public int ZIndex { get; set; }

2
ErsatzTV.Core/Domain/GraphicsElement.cs

@ -11,6 +11,8 @@ public class GraphicsElement @@ -11,6 +11,8 @@ public class GraphicsElement
public List<PlayoutItemGraphicsElement> PlayoutItemGraphicsElements { get; set; }
public List<ProgramScheduleItem> ProgramScheduleItems { get; set; }
public List<ProgramScheduleItemGraphicsElement> ProgramScheduleItemGraphicsElements { get; set; }
public List<BlockItem> BlockItems { get; set; }
public List<BlockItemGraphicsElement> BlockItemGraphicsElements { get; set; }
public List<Deco> Decos { get; set; }
public List<DecoGraphicsElement> DecoGraphicsElements { get; set; }

4
ErsatzTV.Core/Domain/Scheduling/BlockItem.cs

@ -18,4 +18,8 @@ public class BlockItem @@ -18,4 +18,8 @@ public class BlockItem
public PlaybackOrder PlaybackOrder { get; set; }
public bool IncludeInProgramGuide { get; set; }
public bool DisableWatermarks { get; set; }
public List<ChannelWatermark> Watermarks { get; set; }
public List<BlockItemWatermark> BlockItemWatermarks { get; set; }
public List<GraphicsElement> GraphicsElements { get; set; }
public List<BlockItemGraphicsElement> BlockItemGraphicsElements { get; set; }
}

25
ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutBuilder.cs

@ -207,9 +207,32 @@ public class BlockPlayoutBuilder( @@ -207,9 +207,32 @@ public class BlockPlayoutBuilder(
GuideFinish = blockFinish.UtcDateTime,
BlockKey = JsonConvert.SerializeObject(effectiveBlock.BlockKey),
CollectionKey = JsonConvert.SerializeObject(collectionKey, JsonSettings),
CollectionEtag = collectionEtags[collectionKey]
CollectionEtag = collectionEtags[collectionKey],
PlayoutItemWatermarks = [],
PlayoutItemGraphicsElements = []
};
foreach (BlockItemWatermark blockItemWatermark in blockItem.BlockItemWatermarks ?? [])
{
playoutItem.PlayoutItemWatermarks.Add(
new PlayoutItemWatermark
{
PlayoutItem = playoutItem,
WatermarkId = blockItemWatermark.WatermarkId
});
}
foreach (BlockItemGraphicsElement blockItemGraphicsElement in blockItem.BlockItemGraphicsElements ??
[])
{
playoutItem.PlayoutItemGraphicsElements.Add(
new PlayoutItemGraphicsElement
{
PlayoutItem = playoutItem,
GraphicsElementId = blockItemGraphicsElement.GraphicsElementId
});
}
if (effectiveBlock.Block.StopScheduling is BlockStopScheduling.BeforeDurationEnd
&& playoutItem.FinishOffset > blockFinish)
{

6720
ErsatzTV.Infrastructure.MySql/Migrations/20250916020821_Add_BlockItemGraphicsElements.Designer.cs generated

File diff suppressed because it is too large Load Diff

84
ErsatzTV.Infrastructure.MySql/Migrations/20250916020821_Add_BlockItemGraphicsElements.cs

@ -0,0 +1,84 @@ @@ -0,0 +1,84 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Add_BlockItemGraphicsElements : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "BlockItemGraphicsElement",
columns: table => new
{
BlockItemId = table.Column<int>(type: "int", nullable: false),
GraphicsElementId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_BlockItemGraphicsElement", x => new { x.BlockItemId, x.GraphicsElementId });
table.ForeignKey(
name: "FK_BlockItemGraphicsElement_BlockItem_BlockItemId",
column: x => x.BlockItemId,
principalTable: "BlockItem",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_BlockItemGraphicsElement_GraphicsElement_GraphicsElementId",
column: x => x.GraphicsElementId,
principalTable: "GraphicsElement",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "BlockItemWatermark",
columns: table => new
{
BlockItemId = table.Column<int>(type: "int", nullable: false),
WatermarkId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_BlockItemWatermark", x => new { x.BlockItemId, x.WatermarkId });
table.ForeignKey(
name: "FK_BlockItemWatermark_BlockItem_BlockItemId",
column: x => x.BlockItemId,
principalTable: "BlockItem",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_BlockItemWatermark_ChannelWatermark_WatermarkId",
column: x => x.WatermarkId,
principalTable: "ChannelWatermark",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_BlockItemGraphicsElement_GraphicsElementId",
table: "BlockItemGraphicsElement",
column: "GraphicsElementId");
migrationBuilder.CreateIndex(
name: "IX_BlockItemWatermark_WatermarkId",
table: "BlockItemWatermark",
column: "WatermarkId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "BlockItemGraphicsElement");
migrationBuilder.DropTable(
name: "BlockItemWatermark");
}
}
}

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

@ -246,6 +246,36 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -246,6 +246,36 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.ToTable("Artwork", (string)null);
});
modelBuilder.Entity("ErsatzTV.Core.Domain.BlockItemGraphicsElement", b =>
{
b.Property<int>("BlockItemId")
.HasColumnType("int");
b.Property<int>("GraphicsElementId")
.HasColumnType("int");
b.HasKey("BlockItemId", "GraphicsElementId");
b.HasIndex("GraphicsElementId");
b.ToTable("BlockItemGraphicsElement");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.BlockItemWatermark", b =>
{
b.Property<int>("BlockItemId")
.HasColumnType("int");
b.Property<int>("WatermarkId")
.HasColumnType("int");
b.HasKey("BlockItemId", "WatermarkId");
b.HasIndex("WatermarkId");
b.ToTable("BlockItemWatermark");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b =>
{
b.Property<int>("Id")
@ -4195,6 +4225,44 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -4195,6 +4225,44 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("ErsatzTV.Core.Domain.BlockItemGraphicsElement", b =>
{
b.HasOne("ErsatzTV.Core.Domain.Scheduling.BlockItem", "BlockItem")
.WithMany("BlockItemGraphicsElements")
.HasForeignKey("BlockItemId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("ErsatzTV.Core.Domain.GraphicsElement", "GraphicsElement")
.WithMany("BlockItemGraphicsElements")
.HasForeignKey("GraphicsElementId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("BlockItem");
b.Navigation("GraphicsElement");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.BlockItemWatermark", b =>
{
b.HasOne("ErsatzTV.Core.Domain.Scheduling.BlockItem", "BlockItem")
.WithMany("BlockItemWatermarks")
.HasForeignKey("BlockItemId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("ErsatzTV.Core.Domain.ChannelWatermark", "Watermark")
.WithMany("BlockItemWatermarks")
.HasForeignKey("WatermarkId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("BlockItem");
b.Navigation("Watermark");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b =>
{
b.HasOne("ErsatzTV.Core.Domain.FFmpegProfile", "FFmpegProfile")
@ -6174,6 +6242,8 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -6174,6 +6242,8 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
modelBuilder.Entity("ErsatzTV.Core.Domain.ChannelWatermark", b =>
{
b.Navigation("BlockItemWatermarks");
b.Navigation("DecoWatermarks");
b.Navigation("PlayoutItemWatermarks");
@ -6211,6 +6281,8 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -6211,6 +6281,8 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
modelBuilder.Entity("ErsatzTV.Core.Domain.GraphicsElement", b =>
{
b.Navigation("BlockItemGraphicsElements");
b.Navigation("DecoGraphicsElements");
b.Navigation("PlayoutItemGraphicsElements");
@ -6428,6 +6500,13 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -6428,6 +6500,13 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Navigation("Blocks");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.BlockItem", b =>
{
b.Navigation("BlockItemGraphicsElements");
b.Navigation("BlockItemWatermarks");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.Deco", b =>
{
b.Navigation("DecoGraphicsElements");

6549
ErsatzTV.Infrastructure.Sqlite/Migrations/20250916020921_Add_BlockItemGraphicsElements.Designer.cs generated

File diff suppressed because it is too large Load Diff

82
ErsatzTV.Infrastructure.Sqlite/Migrations/20250916020921_Add_BlockItemGraphicsElements.cs

@ -0,0 +1,82 @@ @@ -0,0 +1,82 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Add_BlockItemGraphicsElements : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "BlockItemGraphicsElement",
columns: table => new
{
BlockItemId = table.Column<int>(type: "INTEGER", nullable: false),
GraphicsElementId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_BlockItemGraphicsElement", x => new { x.BlockItemId, x.GraphicsElementId });
table.ForeignKey(
name: "FK_BlockItemGraphicsElement_BlockItem_BlockItemId",
column: x => x.BlockItemId,
principalTable: "BlockItem",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_BlockItemGraphicsElement_GraphicsElement_GraphicsElementId",
column: x => x.GraphicsElementId,
principalTable: "GraphicsElement",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "BlockItemWatermark",
columns: table => new
{
BlockItemId = table.Column<int>(type: "INTEGER", nullable: false),
WatermarkId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_BlockItemWatermark", x => new { x.BlockItemId, x.WatermarkId });
table.ForeignKey(
name: "FK_BlockItemWatermark_BlockItem_BlockItemId",
column: x => x.BlockItemId,
principalTable: "BlockItem",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_BlockItemWatermark_ChannelWatermark_WatermarkId",
column: x => x.WatermarkId,
principalTable: "ChannelWatermark",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_BlockItemGraphicsElement_GraphicsElementId",
table: "BlockItemGraphicsElement",
column: "GraphicsElementId");
migrationBuilder.CreateIndex(
name: "IX_BlockItemWatermark_WatermarkId",
table: "BlockItemWatermark",
column: "WatermarkId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "BlockItemGraphicsElement");
migrationBuilder.DropTable(
name: "BlockItemWatermark");
}
}
}

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

@ -235,6 +235,36 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -235,6 +235,36 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.ToTable("Artwork", (string)null);
});
modelBuilder.Entity("ErsatzTV.Core.Domain.BlockItemGraphicsElement", b =>
{
b.Property<int>("BlockItemId")
.HasColumnType("INTEGER");
b.Property<int>("GraphicsElementId")
.HasColumnType("INTEGER");
b.HasKey("BlockItemId", "GraphicsElementId");
b.HasIndex("GraphicsElementId");
b.ToTable("BlockItemGraphicsElement");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.BlockItemWatermark", b =>
{
b.Property<int>("BlockItemId")
.HasColumnType("INTEGER");
b.Property<int>("WatermarkId")
.HasColumnType("INTEGER");
b.HasKey("BlockItemId", "WatermarkId");
b.HasIndex("WatermarkId");
b.ToTable("BlockItemWatermark");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b =>
{
b.Property<int>("Id")
@ -4024,6 +4054,44 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -4024,6 +4054,44 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("ErsatzTV.Core.Domain.BlockItemGraphicsElement", b =>
{
b.HasOne("ErsatzTV.Core.Domain.Scheduling.BlockItem", "BlockItem")
.WithMany("BlockItemGraphicsElements")
.HasForeignKey("BlockItemId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("ErsatzTV.Core.Domain.GraphicsElement", "GraphicsElement")
.WithMany("BlockItemGraphicsElements")
.HasForeignKey("GraphicsElementId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("BlockItem");
b.Navigation("GraphicsElement");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.BlockItemWatermark", b =>
{
b.HasOne("ErsatzTV.Core.Domain.Scheduling.BlockItem", "BlockItem")
.WithMany("BlockItemWatermarks")
.HasForeignKey("BlockItemId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("ErsatzTV.Core.Domain.ChannelWatermark", "Watermark")
.WithMany("BlockItemWatermarks")
.HasForeignKey("WatermarkId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("BlockItem");
b.Navigation("Watermark");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b =>
{
b.HasOne("ErsatzTV.Core.Domain.FFmpegProfile", "FFmpegProfile")
@ -6003,6 +6071,8 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -6003,6 +6071,8 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
modelBuilder.Entity("ErsatzTV.Core.Domain.ChannelWatermark", b =>
{
b.Navigation("BlockItemWatermarks");
b.Navigation("DecoWatermarks");
b.Navigation("PlayoutItemWatermarks");
@ -6040,6 +6110,8 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -6040,6 +6110,8 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
modelBuilder.Entity("ErsatzTV.Core.Domain.GraphicsElement", b =>
{
b.Navigation("BlockItemGraphicsElements");
b.Navigation("DecoGraphicsElements");
b.Navigation("PlayoutItemGraphicsElements");
@ -6257,6 +6329,13 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -6257,6 +6329,13 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Navigation("Blocks");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.BlockItem", b =>
{
b.Navigation("BlockItemGraphicsElements");
b.Navigation("BlockItemWatermarks");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.Deco", b =>
{
b.Navigation("DecoGraphicsElements");

27
ErsatzTV.Infrastructure/Data/Configurations/Scheduling/BlockItemConfiguration.cs

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
@ -33,5 +34,31 @@ public class BlockItemConfiguration : IEntityTypeConfiguration<BlockItem> @@ -33,5 +34,31 @@ public class BlockItemConfiguration : IEntityTypeConfiguration<BlockItem>
.HasForeignKey(i => i.SmartCollectionId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired(false);
builder.HasMany(c => c.Watermarks)
.WithMany(m => m.BlockItems)
.UsingEntity<BlockItemWatermark>(
j => j.HasOne(ci => ci.Watermark)
.WithMany(mi => mi.BlockItemWatermarks)
.HasForeignKey(ci => ci.WatermarkId)
.OnDelete(DeleteBehavior.Cascade),
j => j.HasOne(ci => ci.BlockItem)
.WithMany(c => c.BlockItemWatermarks)
.HasForeignKey(ci => ci.BlockItemId)
.OnDelete(DeleteBehavior.Cascade),
j => j.HasKey(ci => new { ci.BlockItemId, ci.WatermarkId }));
builder.HasMany(c => c.GraphicsElements)
.WithMany(m => m.BlockItems)
.UsingEntity<BlockItemGraphicsElement>(
j => j.HasOne(ci => ci.GraphicsElement)
.WithMany(mi => mi.BlockItemGraphicsElements)
.HasForeignKey(ci => ci.GraphicsElementId)
.OnDelete(DeleteBehavior.Cascade),
j => j.HasOne(ci => ci.BlockItem)
.WithMany(c => c.BlockItemGraphicsElements)
.HasForeignKey(ci => ci.BlockItemId)
.OnDelete(DeleteBehavior.Cascade),
j => j.HasKey(ci => new { ci.BlockItemId, ci.GraphicsElementId }));
}
}

56
ErsatzTV/Pages/BlockEditor.razor

@ -1,8 +1,10 @@ @@ -1,8 +1,10 @@
@page "/blocks/{Id:int}"
@using ErsatzTV.Application.Graphics
@using ErsatzTV.Application.MediaCollections
@using ErsatzTV.Application.MediaItems
@using ErsatzTV.Application.Scheduling
@using ErsatzTV.Application.Search
@using ErsatzTV.Application.Watermarks
@using ErsatzTV.Core.Domain.Scheduling
@implements IDisposable
@inject NavigationManager NavigationManager
@ -287,6 +289,36 @@ @@ -287,6 +289,36 @@
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Watermarks</MudText>
</div>
<MudSelect T="WatermarkViewModel"
@bind-SelectedValues="_selectedItem.Watermarks"
ToStringFunc="@(wm => wm?.Name)"
Clearable="true"
MultiSelection="true">
@foreach (WatermarkViewModel watermark in _watermarks)
{
<MudSelectItem Value="@watermark">@watermark.Name</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Graphics Elements</MudText>
</div>
<MudSelect T="GraphicsElementViewModel"
@bind-SelectedValues="_selectedItem.GraphicsElements"
ToStringFunc="@(ge => ge?.Name)"
Clearable="true"
MultiSelection="true">
@foreach (GraphicsElementViewModel graphicsElement in _graphicsElements)
{
<MudSelectItem Value="@graphicsElement">@graphicsElement.Name</MudSelectItem>
}
</MudSelect>
</MudStack>
}
else if (_previewItems != null)
{
@ -321,6 +353,8 @@ else if (_previewItems != null) @@ -321,6 +353,8 @@ else if (_previewItems != null)
private BlockItemsEditViewModel _block = new() { Items = [] };
private BlockItemEditViewModel _selectedItem;
private List<WatermarkViewModel> _watermarks = [];
private List<GraphicsElementViewModel> _graphicsElements = [];
private List<PlayoutItemPreviewViewModel> _previewItems;
private int _durationHours;
private int _durationMinutes = 15;
@ -347,6 +381,11 @@ else if (_previewItems != null) @@ -347,6 +381,11 @@ else if (_previewItems != null)
return;
}
_watermarks = await Mediator.Send(new GetAllWatermarks(), token)
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
_graphicsElements = await Mediator.Send(new GetAllGraphicsElements(), token)
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
foreach (BlockViewModel block in maybeBlock)
{
_block = new BlockItemsEditViewModel
@ -451,7 +490,9 @@ else if (_previewItems != null) @@ -451,7 +490,9 @@ else if (_previewItems != null)
MediaItem = item.MediaItem,
PlaybackOrder = item.PlaybackOrder,
IncludeInProgramGuide = item.IncludeInProgramGuide,
DisableWatermarks = item.DisableWatermarks
DisableWatermarks = item.DisableWatermarks,
Watermarks = item.Watermarks,
GraphicsElements = item.GraphicsElements
};
private void AddBlockItem()
@ -460,7 +501,9 @@ else if (_previewItems != null) @@ -460,7 +501,9 @@ else if (_previewItems != null)
{
Index = _block.Items.Map(i => i.Index).DefaultIfEmpty().Max() + 1,
PlaybackOrder = PlaybackOrder.Chronological,
CollectionType = CollectionType.Collection
CollectionType = CollectionType.Collection,
Watermarks = [],
GraphicsElements = []
};
_block.Items.Add(item);
@ -479,7 +522,9 @@ else if (_previewItems != null) @@ -479,7 +522,9 @@ else if (_previewItems != null)
SmartCollection = item.SmartCollection,
MediaItem = item.MediaItem,
IncludeInProgramGuide = item.IncludeInProgramGuide,
DisableWatermarks = item.DisableWatermarks
DisableWatermarks = item.DisableWatermarks,
Watermarks = item.Watermarks.ToList(),
GraphicsElements = item.GraphicsElements.ToList()
};
foreach (BlockItemEditViewModel i in _block.Items.Filter(bi => bi.Index >= newItem.Index))
@ -537,7 +582,10 @@ else if (_previewItems != null) @@ -537,7 +582,10 @@ else if (_previewItems != null)
item.MediaItem?.MediaItemId,
item.PlaybackOrder,
item.IncludeInProgramGuide,
item.DisableWatermarks)).ToList();
item.DisableWatermarks,
item.Watermarks.Map(wm => wm.Id).ToList(),
item.GraphicsElements.Map(wm => wm.Id).ToList())
).ToList();
_block.Minutes = _durationHours * 60 + _durationMinutes;

6
ErsatzTV/ViewModels/BlockItemEditViewModel.cs

@ -1,7 +1,9 @@ @@ -1,7 +1,9 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using ErsatzTV.Application.Graphics;
using ErsatzTV.Application.MediaCollections;
using ErsatzTV.Application.MediaItems;
using ErsatzTV.Application.Watermarks;
using ErsatzTV.Core.Domain;
namespace ErsatzTV.ViewModels;
@ -62,6 +64,10 @@ public class BlockItemEditViewModel : INotifyPropertyChanged @@ -62,6 +64,10 @@ public class BlockItemEditViewModel : INotifyPropertyChanged
public bool DisableWatermarks { get; set; }
public IEnumerable<WatermarkViewModel> Watermarks { get; set; }
public IEnumerable<GraphicsElementViewModel> GraphicsElements { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) =>

Loading…
Cancel
Save