allow selecting multiple watermarks on decos (#2287)
* load fonts on demand * add new table * populate new table * edit and use multiple watermarks in deco * remove old field * update changelog
This commit is contained in:
+1
-1
@@ -36,9 +36,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
- YAML playouts in particular should build significantly faster
|
||||
|
||||
### Changed
|
||||
- Allow multiple watermarks on a single playout item
|
||||
- Allow multiple watermarks in playback troubleshooting
|
||||
- Classic schedules: allow selecting multiple watermarks on schedule items
|
||||
- Block schedules: allow selecting multiple watermarks on decos
|
||||
- YAML playout: `watermark` instruction changes:
|
||||
- When value is `true`, will add named watermark to list of active watermarks
|
||||
- When value is `false` and `name` is specified, will remove named watermark from list of active watermarks
|
||||
|
||||
@@ -28,7 +28,8 @@ public class CreateDecoHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||
await ValidateDecoName(dbContext, request).MapT(name => new Deco
|
||||
{
|
||||
DecoGroupId = request.DecoGroupId,
|
||||
Name = name
|
||||
Name = name,
|
||||
DecoWatermarks = []
|
||||
});
|
||||
|
||||
private static async Task<Validation<BaseError, string>> ValidateDecoName(
|
||||
|
||||
@@ -9,7 +9,7 @@ public record UpdateDeco(
|
||||
int DecoGroupId,
|
||||
string Name,
|
||||
DecoMode WatermarkMode,
|
||||
int? WatermarkId,
|
||||
List<int> WatermarkIds,
|
||||
bool UseWatermarkDuringFiller,
|
||||
DecoMode DefaultFillerMode,
|
||||
ProgramScheduleItemCollectionType DefaultFillerCollectionType,
|
||||
@@ -24,4 +24,4 @@ public record UpdateDeco(
|
||||
int? DeadAirFallbackMediaItemId,
|
||||
int? DeadAirFallbackMultiCollectionId,
|
||||
int? DeadAirFallbackSmartCollectionId)
|
||||
: IRequest<Either<BaseError, DecoViewModel>>;
|
||||
: IRequest<Either<BaseError, Unit>>;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Domain.Scheduling;
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
using ErsatzTV.Infrastructure.Extensions;
|
||||
@@ -7,16 +8,16 @@ using Microsoft.EntityFrameworkCore;
|
||||
namespace ErsatzTV.Application.Scheduling;
|
||||
|
||||
public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||
: IRequestHandler<UpdateDeco, Either<BaseError, DecoViewModel>>
|
||||
: IRequestHandler<UpdateDeco, Either<BaseError, Unit>>
|
||||
{
|
||||
public async Task<Either<BaseError, DecoViewModel>> Handle(UpdateDeco request, CancellationToken cancellationToken)
|
||||
public async Task<Either<BaseError, Unit>> Handle(UpdateDeco request, CancellationToken cancellationToken)
|
||||
{
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
Validation<BaseError, Deco> validation = await Validate(dbContext, request);
|
||||
return await validation.Apply(ps => ApplyUpdateRequest(dbContext, ps, request));
|
||||
}
|
||||
|
||||
private static async Task<DecoViewModel> ApplyUpdateRequest(
|
||||
private static async Task<Unit> ApplyUpdateRequest(
|
||||
TvContext dbContext,
|
||||
Deco existing,
|
||||
UpdateDeco request)
|
||||
@@ -25,10 +26,30 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||
|
||||
// watermark
|
||||
existing.WatermarkMode = request.WatermarkMode;
|
||||
existing.WatermarkId = request.WatermarkMode is DecoMode.Override ? request.WatermarkId : null;
|
||||
existing.UseWatermarkDuringFiller =
|
||||
request.WatermarkMode is DecoMode.Override && request.UseWatermarkDuringFiller;
|
||||
|
||||
if (request.WatermarkMode is DecoMode.Override)
|
||||
{
|
||||
// this is different than schedule item/playout item because we have to merge watermark ids
|
||||
var toAdd = request.WatermarkIds.Where(id => existing.DecoWatermarks.All(wm => wm.WatermarkId != id));
|
||||
var toRemove = existing.DecoWatermarks.Where(wm => !request.WatermarkIds.Contains(wm.WatermarkId));
|
||||
existing.DecoWatermarks.RemoveAll(toRemove.Contains);
|
||||
foreach (var watermarkId in toAdd)
|
||||
{
|
||||
existing.DecoWatermarks.Add(
|
||||
new DecoWatermark
|
||||
{
|
||||
DecoId = existing.Id,
|
||||
WatermarkId = watermarkId
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
existing.DecoWatermarks.Clear();
|
||||
}
|
||||
|
||||
// default filler
|
||||
existing.DefaultFillerMode = request.DefaultFillerMode;
|
||||
existing.DefaultFillerCollectionType = request.DefaultFillerCollectionType;
|
||||
@@ -64,7 +85,7 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
return Mapper.ProjectToViewModel(existing);
|
||||
return Unit.Default;
|
||||
}
|
||||
|
||||
private static async Task<Validation<BaseError, Deco>> Validate(TvContext dbContext, UpdateDeco request) =>
|
||||
@@ -75,6 +96,7 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||
TvContext dbContext,
|
||||
UpdateDeco request) =>
|
||||
dbContext.Decos
|
||||
.Include(d => d.DecoWatermarks)
|
||||
.SelectOneAsync(d => d.Id, d => d.Id == request.DecoId)
|
||||
.Map(o => o.ToValidation<BaseError>("Deco does not exist"));
|
||||
|
||||
@@ -88,6 +110,7 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||
}
|
||||
|
||||
Option<Deco> maybeExisting = await dbContext.Decos
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(d =>
|
||||
d.Id != request.DecoId && d.DecoGroupId == request.DecoGroupId && d.Name == request.Name)
|
||||
.Map(Optional);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using ErsatzTV.Application.Watermarks;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Domain.Scheduling;
|
||||
|
||||
@@ -8,7 +9,7 @@ public record DecoViewModel(
|
||||
int DecoGroupId,
|
||||
string Name,
|
||||
DecoMode WatermarkMode,
|
||||
int? WatermarkId,
|
||||
List<WatermarkViewModel> Watermarks,
|
||||
bool UseWatermarkDuringFiller,
|
||||
DecoMode DefaultFillerMode,
|
||||
ProgramScheduleItemCollectionType DefaultFillerCollectionType,
|
||||
|
||||
@@ -85,7 +85,7 @@ internal static class Mapper
|
||||
deco.DecoGroupId,
|
||||
deco.Name,
|
||||
deco.WatermarkMode,
|
||||
deco.WatermarkId,
|
||||
deco.DecoWatermarks.Map(wm => Watermarks.Mapper.ProjectToViewModel(wm.Watermark)).ToList(),
|
||||
deco.UseWatermarkDuringFiller,
|
||||
deco.DefaultFillerMode,
|
||||
deco.DefaultFillerCollectionType,
|
||||
|
||||
@@ -11,6 +11,9 @@ public class GetDecoByIdHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||
{
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
return await dbContext.Decos
|
||||
.AsNoTracking()
|
||||
.Include(d => d.DecoWatermarks)
|
||||
.ThenInclude(d => d.Watermark)
|
||||
.SelectOneAsync(b => b.Id, b => b.Id == request.DecoId)
|
||||
.MapT(Mapper.ProjectToViewModel);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ public class GetDecoByPlayoutIdHandler(IDbContextFactory<TvContext> dbContextFac
|
||||
{
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
return await dbContext.Playouts
|
||||
.AsNoTracking()
|
||||
.Include(p => p.Deco)
|
||||
.ThenInclude(d => d.DecoWatermarks)
|
||||
.ThenInclude(d => d.Watermark)
|
||||
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId && p.DecoId != null)
|
||||
.MapT(p => Mapper.ProjectToViewModel(p.Deco));
|
||||
}
|
||||
|
||||
@@ -12,8 +12,10 @@ public class GetDecosByDecoGroupIdHandler(IDbContextFactory<TvContext> dbContext
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
List<Deco> decos = await dbContext.Decos
|
||||
.Filter(b => b.DecoGroupId == request.DecoGroupId)
|
||||
.AsNoTracking()
|
||||
.Include(d => d.DecoWatermarks)
|
||||
.ThenInclude(d => d.Watermark)
|
||||
.Filter(b => b.DecoGroupId == request.DecoGroupId)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return decos.Map(Mapper.ProjectToViewModel).ToList();
|
||||
|
||||
+9
-5
@@ -86,6 +86,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
|
||||
// get playout deco
|
||||
.Include(i => i.Playout)
|
||||
.ThenInclude(p => p.Deco)
|
||||
.ThenInclude(d => d.DecoWatermarks)
|
||||
.ThenInclude(d => d.Watermark)
|
||||
|
||||
// get graphics elements
|
||||
@@ -98,6 +99,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
|
||||
.ThenInclude(t => t.DecoTemplate)
|
||||
.ThenInclude(t => t.Items)
|
||||
.ThenInclude(i => i.Deco)
|
||||
.ThenInclude(d => d.DecoWatermarks)
|
||||
.ThenInclude(d => d.Watermark)
|
||||
.Include(i => i.MediaItem)
|
||||
.ThenInclude(mi => (mi as Episode).EpisodeMetadata)
|
||||
@@ -194,6 +196,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
|
||||
|
||||
// get playout deco
|
||||
.Include(p => p.Deco)
|
||||
.ThenInclude(d => d.DecoWatermarks)
|
||||
.ThenInclude(d => d.Watermark)
|
||||
|
||||
// get playout templates (and deco templates/decos)
|
||||
@@ -201,6 +204,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
|
||||
.ThenInclude(t => t.DecoTemplate)
|
||||
.ThenInclude(t => t.Items)
|
||||
.ThenInclude(i => i.Deco)
|
||||
.ThenInclude(d => d.DecoWatermarks)
|
||||
.ThenInclude(d => d.Watermark)
|
||||
.SelectOneAsync(p => p.ChannelId, p => p.ChannelId == channel.Id);
|
||||
|
||||
@@ -259,9 +263,9 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
|
||||
case DisableWatermark:
|
||||
disableWatermarks = true;
|
||||
break;
|
||||
case CustomWatermark watermark:
|
||||
case CustomWatermarks watermarks:
|
||||
playoutItemWatermarks.Clear();
|
||||
playoutItemWatermarks.Add(watermark.Watermark);
|
||||
playoutItemWatermarks.AddRange(watermarks.Watermarks);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -765,7 +769,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
|
||||
if (playoutItem.FillerKind is FillerKind.None || templateDeco.UseWatermarkDuringFiller)
|
||||
{
|
||||
_logger.LogDebug("Watermark will come from template deco (override)");
|
||||
return new CustomWatermark(templateDeco.Watermark);
|
||||
return new CustomWatermarks(templateDeco.DecoWatermarks.Map(dwm => dwm.Watermark).ToList());
|
||||
}
|
||||
|
||||
_logger.LogDebug("Watermark is disabled by template deco during filler");
|
||||
@@ -788,7 +792,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
|
||||
if (playoutItem.FillerKind is FillerKind.None || playoutDeco.UseWatermarkDuringFiller)
|
||||
{
|
||||
_logger.LogDebug("Watermark will come from playout deco (override)");
|
||||
return new CustomWatermark(playoutDeco.Watermark);
|
||||
return new CustomWatermarks(playoutDeco.DecoWatermarks.Map(dwm => dwm.Watermark).ToList());
|
||||
}
|
||||
|
||||
_logger.LogDebug("Watermark is disabled by playout deco during filler");
|
||||
@@ -891,7 +895,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
|
||||
|
||||
private sealed record DisableWatermark : WatermarkResult;
|
||||
|
||||
private sealed record CustomWatermark(ChannelWatermark Watermark) : WatermarkResult;
|
||||
private sealed record CustomWatermarks(List<ChannelWatermark> Watermarks) : WatermarkResult;
|
||||
|
||||
private abstract record DeadAirFallbackResult;
|
||||
|
||||
|
||||
@@ -126,6 +126,10 @@ public class StartTroubleshootingPlaybackHandler(
|
||||
notifier.NotifyFailed(request.SessionId);
|
||||
}
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using ErsatzTV.FFmpeg.State;
|
||||
using ErsatzTV.Core.Domain.Scheduling;
|
||||
using ErsatzTV.FFmpeg.State;
|
||||
|
||||
namespace ErsatzTV.Core.Domain;
|
||||
|
||||
@@ -24,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<Deco> Decos { get; set; }
|
||||
public List<DecoWatermark> DecoWatermarks { get; set; }
|
||||
public int ZIndex { get; set; }
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using ErsatzTV.Core.Domain.Scheduling;
|
||||
|
||||
namespace ErsatzTV.Core.Domain;
|
||||
|
||||
public class DecoWatermark
|
||||
{
|
||||
public int DecoId { get; set; }
|
||||
public Deco Deco { get; set; }
|
||||
public int WatermarkId { get; set; }
|
||||
public ChannelWatermark Watermark { get; set; }
|
||||
}
|
||||
@@ -4,6 +4,6 @@ public class PlayoutItemWatermark
|
||||
{
|
||||
public int PlayoutItemId { get; set; }
|
||||
public PlayoutItem PlayoutItem { get; set; }
|
||||
public int? WatermarkId { get; set; }
|
||||
public int WatermarkId { get; set; }
|
||||
public ChannelWatermark Watermark { get; set; }
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@ public class ProgramScheduleItemWatermark
|
||||
{
|
||||
public int ProgramScheduleItemId { get; set; }
|
||||
public ProgramScheduleItem ProgramScheduleItem { get; set; }
|
||||
public int? WatermarkId { get; set; }
|
||||
public int WatermarkId { get; set; }
|
||||
public ChannelWatermark Watermark { get; set; }
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ public class Deco
|
||||
|
||||
// watermark
|
||||
public DecoMode WatermarkMode { get; set; }
|
||||
public int? WatermarkId { get; set; }
|
||||
public ChannelWatermark Watermark { get; set; }
|
||||
public List<ChannelWatermark> Watermarks { get; set; }
|
||||
public List<DecoWatermark> DecoWatermarks { get; set; }
|
||||
public bool UseWatermarkDuringFiller { get; set; }
|
||||
|
||||
// default filler
|
||||
|
||||
+6344
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,51 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Add_DecoWatermarks : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DecoWatermark",
|
||||
columns: table => new
|
||||
{
|
||||
DecoId = table.Column<int>(type: "int", nullable: false),
|
||||
WatermarkId = table.Column<int>(type: "int", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DecoWatermark", x => new { x.DecoId, x.WatermarkId });
|
||||
table.ForeignKey(
|
||||
name: "FK_DecoWatermark_ChannelWatermark_WatermarkId",
|
||||
column: x => x.WatermarkId,
|
||||
principalTable: "ChannelWatermark",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_DecoWatermark_Deco_DecoId",
|
||||
column: x => x.DecoId,
|
||||
principalTable: "Deco",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DecoWatermark_WatermarkId",
|
||||
table: "DecoWatermark",
|
||||
column: "WatermarkId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DecoWatermark");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+6344
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Populate_DecoWatermarks : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(
|
||||
@"INSERT INTO `DecoWatermark` (`DecoId`, `WatermarkId`)
|
||||
SELECT `Id`, `WatermarkId` FROM `Deco` WHERE `WatermarkId` IS NOT NULL");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+6332
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Remove_DecoWatermark : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Deco_ChannelWatermark_WatermarkId",
|
||||
table: "Deco");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Deco_WatermarkId",
|
||||
table: "Deco");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WatermarkId",
|
||||
table: "Deco");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "WatermarkId",
|
||||
table: "Deco",
|
||||
type: "int",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Deco_WatermarkId",
|
||||
table: "Deco",
|
||||
column: "WatermarkId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Deco_ChannelWatermark_WatermarkId",
|
||||
table: "Deco",
|
||||
column: "WatermarkId",
|
||||
principalTable: "ChannelWatermark",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -464,6 +464,21 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
b.ToTable("ConfigElement", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.DecoWatermark", b =>
|
||||
{
|
||||
b.Property<int>("DecoId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("WatermarkId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("DecoId", "WatermarkId");
|
||||
|
||||
b.HasIndex("WatermarkId");
|
||||
|
||||
b.ToTable("DecoWatermark");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Director", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -2512,9 +2527,6 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
b.Property<bool>("UseWatermarkDuringFiller")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int?>("WatermarkId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("WatermarkMode")
|
||||
.HasColumnType("int");
|
||||
|
||||
@@ -2536,8 +2548,6 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
|
||||
b.HasIndex("DefaultFillerSmartCollectionId");
|
||||
|
||||
b.HasIndex("WatermarkId");
|
||||
|
||||
b.HasIndex("DecoGroupId", "Name")
|
||||
.IsUnique();
|
||||
|
||||
@@ -4058,6 +4068,25 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
b.Navigation("MediaItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.DecoWatermark", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Scheduling.Deco", "Deco")
|
||||
.WithMany("DecoWatermarks")
|
||||
.HasForeignKey("DecoId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("ErsatzTV.Core.Domain.ChannelWatermark", "Watermark")
|
||||
.WithMany("DecoWatermarks")
|
||||
.HasForeignKey("WatermarkId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Deco");
|
||||
|
||||
b.Navigation("Watermark");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Director", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.EpisodeMetadata", null)
|
||||
@@ -5072,11 +5101,6 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
.WithMany()
|
||||
.HasForeignKey("DefaultFillerSmartCollectionId");
|
||||
|
||||
b.HasOne("ErsatzTV.Core.Domain.ChannelWatermark", "Watermark")
|
||||
.WithMany()
|
||||
.HasForeignKey("WatermarkId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("DeadAirFallbackCollection");
|
||||
|
||||
b.Navigation("DeadAirFallbackMediaItem");
|
||||
@@ -5094,8 +5118,6 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
b.Navigation("DefaultFillerMultiCollection");
|
||||
|
||||
b.Navigation("DefaultFillerSmartCollection");
|
||||
|
||||
b.Navigation("Watermark");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.DecoTemplate", b =>
|
||||
@@ -5853,6 +5875,8 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ChannelWatermark", b =>
|
||||
{
|
||||
b.Navigation("DecoWatermarks");
|
||||
|
||||
b.Navigation("PlayoutItemWatermarks");
|
||||
|
||||
b.Navigation("ProgramScheduleItemWatermarks");
|
||||
@@ -6099,6 +6123,8 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.Deco", b =>
|
||||
{
|
||||
b.Navigation("DecoWatermarks");
|
||||
|
||||
b.Navigation("Playouts");
|
||||
});
|
||||
|
||||
|
||||
+6179
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Add_DecoWatermarks : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DecoWatermark",
|
||||
columns: table => new
|
||||
{
|
||||
DecoId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
WatermarkId = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DecoWatermark", x => new { x.DecoId, x.WatermarkId });
|
||||
table.ForeignKey(
|
||||
name: "FK_DecoWatermark_ChannelWatermark_WatermarkId",
|
||||
column: x => x.WatermarkId,
|
||||
principalTable: "ChannelWatermark",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_DecoWatermark_Deco_DecoId",
|
||||
column: x => x.DecoId,
|
||||
principalTable: "Deco",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DecoWatermark_WatermarkId",
|
||||
table: "DecoWatermark",
|
||||
column: "WatermarkId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DecoWatermark");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+6179
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Populate_DecoWatermarks : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(
|
||||
@"INSERT INTO `DecoWatermark` (`DecoId`, `WatermarkId`)
|
||||
SELECT `Id`, `WatermarkId` FROM `Deco` WHERE `WatermarkId` IS NOT NULL");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+6167
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Remove_DecoWatermark : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Deco_ChannelWatermark_WatermarkId",
|
||||
table: "Deco");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Deco_WatermarkId",
|
||||
table: "Deco");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WatermarkId",
|
||||
table: "Deco");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "WatermarkId",
|
||||
table: "Deco",
|
||||
type: "INTEGER",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Deco_WatermarkId",
|
||||
table: "Deco",
|
||||
column: "WatermarkId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Deco_ChannelWatermark_WatermarkId",
|
||||
table: "Deco",
|
||||
column: "WatermarkId",
|
||||
principalTable: "ChannelWatermark",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -445,6 +445,21 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
b.ToTable("ConfigElement", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.DecoWatermark", b =>
|
||||
{
|
||||
b.Property<int>("DecoId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("WatermarkId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("DecoId", "WatermarkId");
|
||||
|
||||
b.HasIndex("WatermarkId");
|
||||
|
||||
b.ToTable("DecoWatermark");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Director", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -2393,9 +2408,6 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
b.Property<bool>("UseWatermarkDuringFiller")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("WatermarkId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("WatermarkMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@@ -2417,8 +2429,6 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
|
||||
b.HasIndex("DefaultFillerSmartCollectionId");
|
||||
|
||||
b.HasIndex("WatermarkId");
|
||||
|
||||
b.HasIndex("DecoGroupId", "Name")
|
||||
.IsUnique();
|
||||
|
||||
@@ -3893,6 +3903,25 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
b.Navigation("MediaItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.DecoWatermark", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Scheduling.Deco", "Deco")
|
||||
.WithMany("DecoWatermarks")
|
||||
.HasForeignKey("DecoId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("ErsatzTV.Core.Domain.ChannelWatermark", "Watermark")
|
||||
.WithMany("DecoWatermarks")
|
||||
.HasForeignKey("WatermarkId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Deco");
|
||||
|
||||
b.Navigation("Watermark");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Director", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.EpisodeMetadata", null)
|
||||
@@ -4907,11 +4936,6 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
.WithMany()
|
||||
.HasForeignKey("DefaultFillerSmartCollectionId");
|
||||
|
||||
b.HasOne("ErsatzTV.Core.Domain.ChannelWatermark", "Watermark")
|
||||
.WithMany()
|
||||
.HasForeignKey("WatermarkId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("DeadAirFallbackCollection");
|
||||
|
||||
b.Navigation("DeadAirFallbackMediaItem");
|
||||
@@ -4929,8 +4953,6 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
b.Navigation("DefaultFillerMultiCollection");
|
||||
|
||||
b.Navigation("DefaultFillerSmartCollection");
|
||||
|
||||
b.Navigation("Watermark");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.DecoTemplate", b =>
|
||||
@@ -5688,6 +5710,8 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ChannelWatermark", b =>
|
||||
{
|
||||
b.Navigation("DecoWatermarks");
|
||||
|
||||
b.Navigation("PlayoutItemWatermarks");
|
||||
|
||||
b.Navigation("ProgramScheduleItemWatermarks");
|
||||
@@ -5934,6 +5958,8 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.Deco", b =>
|
||||
{
|
||||
b.Navigation("DecoWatermarks");
|
||||
|
||||
b.Navigation("Playouts");
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Domain.Scheduling;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
@@ -19,12 +20,6 @@ public class DecoConfiguration : IEntityTypeConfiguration<Deco>
|
||||
.OnDelete(DeleteBehavior.SetNull)
|
||||
.IsRequired(false);
|
||||
|
||||
builder.HasOne(d => d.Watermark)
|
||||
.WithMany()
|
||||
.HasForeignKey(d => d.WatermarkId)
|
||||
.OnDelete(DeleteBehavior.SetNull)
|
||||
.IsRequired(false);
|
||||
|
||||
builder.HasOne(d => d.DeadAirFallbackCollection)
|
||||
.WithMany()
|
||||
.HasForeignKey(d => d.DeadAirFallbackCollectionId)
|
||||
@@ -48,5 +43,18 @@ public class DecoConfiguration : IEntityTypeConfiguration<Deco>
|
||||
.HasForeignKey(d => d.DeadAirFallbackSmartCollectionId)
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired(false);
|
||||
|
||||
builder.HasMany(c => c.Watermarks)
|
||||
.WithMany(m => m.Decos)
|
||||
.UsingEntity<DecoWatermark>(
|
||||
j => j.HasOne(ci => ci.Watermark)
|
||||
.WithMany(mi => mi.DecoWatermarks)
|
||||
.HasForeignKey(ci => ci.WatermarkId)
|
||||
.OnDelete(DeleteBehavior.Cascade),
|
||||
j => j.HasOne(ci => ci.Deco)
|
||||
.WithMany(c => c.DecoWatermarks)
|
||||
.HasForeignKey(ci => ci.DecoId)
|
||||
.OnDelete(DeleteBehavior.Cascade),
|
||||
j => j.HasKey(ci => new { ci.DecoId, ci.WatermarkId }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,30 +7,31 @@ namespace ErsatzTV.Infrastructure.Streaming;
|
||||
public static class GraphicsEngineFonts
|
||||
{
|
||||
private static readonly FontCollection CustomFontCollection = new();
|
||||
private static readonly ConcurrentDictionary<string, FontFamily> CustomFontFamilies = new();
|
||||
|
||||
private static bool _fontsLoaded;
|
||||
private static readonly ConcurrentDictionary<string, FontFamily> CustomFontFamilies
|
||||
= new(StringComparer.OrdinalIgnoreCase);
|
||||
private static readonly System.Collections.Generic.HashSet<string> LoadedFontFiles
|
||||
= new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public static void LoadFonts(string fontsFolder)
|
||||
{
|
||||
if (_fontsLoaded)
|
||||
foreach (var file in Directory.EnumerateFiles(fontsFolder, "*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
return;
|
||||
if (!file.EndsWith(".ttf", StringComparison.OrdinalIgnoreCase) &&
|
||||
!file.EndsWith(".otf", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var file in Directory.GetFiles(fontsFolder, "*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (file.EndsWith(".ttf", StringComparison.OrdinalIgnoreCase) ||
|
||||
file.EndsWith(".otf", StringComparison.OrdinalIgnoreCase))
|
||||
if (!LoadedFontFiles.Add(file))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var fontFamily = CustomFontCollection.Add(file, CultureInfo.CurrentCulture);
|
||||
CustomFontFamilies.TryAdd(fontFamily.Name, fontFamily);
|
||||
}
|
||||
}
|
||||
|
||||
_fontsLoaded = true;
|
||||
}
|
||||
|
||||
public static Font GetFont(string fontFamilyName, float fontSize, FontStyle style)
|
||||
{
|
||||
// try custom fonts
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<div class="d-flex">
|
||||
<MudText>Enable Default Deco</MudText>
|
||||
</div>
|
||||
<MudCheckBox @bind-Value="_enableDefaultDeco" Color="Color.Primary" Dense="true"/>
|
||||
<MudCheckBox @bind-Value="_enableDefaultDeco" Dense="true"/>
|
||||
</MudStack>
|
||||
@if (_enableDefaultDeco)
|
||||
{
|
||||
@@ -53,7 +53,9 @@
|
||||
<div class="d-flex">
|
||||
<MudText>Deco</MudText>
|
||||
</div>
|
||||
<MudSelect @bind-Value="_defaultDeco" For="@(() => _defaultDeco)">
|
||||
<MudSelect @bind-Value="_defaultDeco"
|
||||
For="@(() => _defaultDeco)"
|
||||
ToStringFunc="@(d => d?.Name)">
|
||||
@foreach (DecoViewModel deco in _decos)
|
||||
{
|
||||
<MudSelectItem Value="@deco">@deco.Name</MudSelectItem>
|
||||
|
||||
@@ -44,12 +44,15 @@
|
||||
<div class="d-flex">
|
||||
<MudText>Watermark Override</MudText>
|
||||
</div>
|
||||
<MudSelect Disabled="@(_deco.WatermarkMode != DecoMode.Override)" @bind-Value="_deco.WatermarkId" For="@(() => _deco.WatermarkId)"
|
||||
Clearable="true">
|
||||
<MudSelectItem T="int?" Value="@((int?)null)">(none)</MudSelectItem>
|
||||
<MudSelect T="WatermarkViewModel"
|
||||
@bind-SelectedValues="_deco.Watermarks"
|
||||
Disabled="@(_deco.WatermarkMode != DecoMode.Override)"
|
||||
ToStringFunc="@(wm => wm?.Name)"
|
||||
Clearable="true"
|
||||
MultiSelection="true">
|
||||
@foreach (WatermarkViewModel watermark in _watermarks)
|
||||
{
|
||||
<MudSelectItem T="int?" Value="@watermark.Id">@watermark.Name</MudSelectItem>
|
||||
<MudSelectItem Value="@watermark">@watermark.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudStack>
|
||||
@@ -60,7 +63,6 @@
|
||||
<MudCheckBox T="bool"
|
||||
Disabled="@(_deco.WatermarkMode != DecoMode.Override)"
|
||||
@bind-Value="_deco.UseWatermarkDuringFiller"
|
||||
Color="Color.Primary"
|
||||
Dense="true" />
|
||||
</MudStack>
|
||||
<MudText Typo="Typo.h5" Class="mt-10 mb-2">Default Filler</MudText>
|
||||
@@ -389,7 +391,7 @@
|
||||
Name = deco.Name,
|
||||
DecoGroupId = deco.DecoGroupId,
|
||||
WatermarkMode = deco.WatermarkMode,
|
||||
WatermarkId = deco.WatermarkId,
|
||||
Watermarks = deco.Watermarks,
|
||||
UseWatermarkDuringFiller = deco.UseWatermarkDuringFiller,
|
||||
|
||||
DefaultFillerMode = deco.DefaultFillerMode,
|
||||
@@ -435,7 +437,7 @@
|
||||
_deco.DecoGroupId,
|
||||
_deco.Name,
|
||||
_deco.WatermarkMode,
|
||||
_deco.WatermarkId,
|
||||
_deco.Watermarks.Map(wm => wm.Id).ToList(),
|
||||
_deco.UseWatermarkDuringFiller,
|
||||
_deco.DefaultFillerMode,
|
||||
_deco.DefaultFillerCollectionType,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using ErsatzTV.Application.MediaCollections;
|
||||
using ErsatzTV.Application.MediaItems;
|
||||
using ErsatzTV.Application.Watermarks;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Domain.Scheduling;
|
||||
|
||||
@@ -10,7 +11,7 @@ public class DecoEditViewModel
|
||||
public int DecoGroupId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public DecoMode WatermarkMode { get; set; }
|
||||
public int? WatermarkId { get; set; }
|
||||
public IEnumerable<WatermarkViewModel> Watermarks { get; set; }
|
||||
public bool UseWatermarkDuringFiller { get; set; }
|
||||
|
||||
public DecoMode DefaultFillerMode { get; set; }
|
||||
|
||||
Reference in New Issue
Block a user