Browse Source

add watermark z-index (#2279)

pull/2280/head
Jason Dove 5 months ago committed by GitHub
parent
commit
14539d00d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 24
      ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs
  3. 3
      ErsatzTV.Application/Watermarks/Commands/CreateWatermark.cs
  4. 3
      ErsatzTV.Application/Watermarks/Commands/CreateWatermarkHandler.cs
  5. 3
      ErsatzTV.Application/Watermarks/Commands/UpdateWatermark.cs
  6. 1
      ErsatzTV.Application/Watermarks/Commands/UpdateWatermarkHandler.cs
  7. 3
      ErsatzTV.Application/Watermarks/Mapper.cs
  8. 4
      ErsatzTV.Application/Watermarks/WatermarkViewModel.cs
  9. 1
      ErsatzTV.Core/Domain/ChannelWatermark.cs
  10. 2
      ErsatzTV.Core/Interfaces/Streaming/IGraphicsElement.cs
  11. 6214
      ErsatzTV.Infrastructure.MySql/Migrations/20250808002108_Add_ChannelWatermarkZIndex.Designer.cs
  12. 29
      ErsatzTV.Infrastructure.MySql/Migrations/20250808002108_Add_ChannelWatermarkZIndex.cs
  13. 3
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  14. 6051
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250808002026_Add_ChannelWatermarkZIndex.Designer.cs
  15. 29
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250808002026_Add_ChannelWatermarkZIndex.cs
  16. 3
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  17. 2
      ErsatzTV.Infrastructure/Streaming/GraphicsEngine.cs
  18. 3
      ErsatzTV.Infrastructure/Streaming/WatermarkElement.cs
  19. 9
      ErsatzTV/Pages/WatermarkEditor.razor
  20. 8
      ErsatzTV/ViewModels/WatermarkEditViewModel.cs

2
CHANGELOG.md

@ -17,6 +17,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -17,6 +17,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- The expression can also use functions:
- `LinearFadeDuration(time, start, fadeSeconds, peakSeconds)`
- `LinearFadePoints(time, start, peakStart, peakEnd, end)`
- Add `Z-Index` to watermark editor
- The graphics engine will order by z-index when overlaying watermarks
### Fix
- Fix database operations that were slowing down playout builds

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

@ -134,7 +134,29 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -134,7 +134,29 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
if (result.AddedItems.Count > 0)
{
changeCount += 1;
await dbContext.BulkInsertAsync(result.AddedItems, cancellationToken: cancellationToken);
var bulkConfig = new BulkConfig();
bool anyWatermarks = result.AddedItems.Any(i => i.PlayoutItemWatermarks is not null && i.PlayoutItemWatermarks.Count > 0);
if (anyWatermarks)
{
bulkConfig.SetOutputIdentity = true;
}
await dbContext.BulkInsertAsync(result.AddedItems, bulkConfig, cancellationToken: cancellationToken);
if (anyWatermarks)
{
// copy playout item ids back to watermarks
var allWatermarks = result.AddedItems.SelectMany(item =>
item.PlayoutItemWatermarks.Select(watermark =>
{
watermark.PlayoutItemId = item.Id;
watermark.PlayoutItem = null;
return watermark;
})
).ToList();
await dbContext.BulkInsertAsync(allWatermarks, cancellationToken: cancellationToken);
}
}
if (result.HistoryToRemove.Count > 0)

3
ErsatzTV.Application/Watermarks/Commands/CreateWatermark.cs

@ -19,6 +19,7 @@ public record CreateWatermark( @@ -19,6 +19,7 @@ public record CreateWatermark(
int DurationSeconds,
int Opacity,
bool PlaceWithinSourceContent,
string OpacityExpression) : IRequest<Either<BaseError, CreateWatermarkResult>>;
string OpacityExpression,
int ZIndex) : IRequest<Either<BaseError, CreateWatermarkResult>>;
public record CreateWatermarkResult(int WatermarkId) : EntityIdResult(WatermarkId);

3
ErsatzTV.Application/Watermarks/Commands/CreateWatermarkHandler.cs

@ -56,7 +56,8 @@ public class CreateWatermarkHandler : IRequestHandler<CreateWatermark, Either<Ba @@ -56,7 +56,8 @@ public class CreateWatermarkHandler : IRequestHandler<CreateWatermark, Either<Ba
DurationSeconds = request.DurationSeconds,
Opacity = request.Opacity,
PlaceWithinSourceContent = request.PlaceWithinSourceContent,
OpacityExpression = request.OpacityExpression
OpacityExpression = request.OpacityExpression,
ZIndex = request.ZIndex
};
if (request.ImageSource == ChannelWatermarkImageSource.Custom)

3
ErsatzTV.Application/Watermarks/Commands/UpdateWatermark.cs

@ -20,6 +20,7 @@ public record UpdateWatermark( @@ -20,6 +20,7 @@ public record UpdateWatermark(
int DurationSeconds,
int Opacity,
bool PlaceWithinSourceContent,
string OpacityExpression) : IRequest<Either<BaseError, UpdateWatermarkResult>>;
string OpacityExpression,
int ZIndex) : IRequest<Either<BaseError, UpdateWatermarkResult>>;
public record UpdateWatermarkResult(int WatermarkId) : EntityIdResult(WatermarkId);

1
ErsatzTV.Application/Watermarks/Commands/UpdateWatermarkHandler.cs

@ -54,6 +54,7 @@ public class UpdateWatermarkHandler : IRequestHandler<UpdateWatermark, Either<Ba @@ -54,6 +54,7 @@ public class UpdateWatermarkHandler : IRequestHandler<UpdateWatermark, Either<Ba
p.Opacity = update.Opacity;
p.PlaceWithinSourceContent = update.PlaceWithinSourceContent;
p.OpacityExpression = update.Mode is ChannelWatermarkMode.OpacityExpression ? update.OpacityExpression : null;
p.ZIndex = update.ZIndex;
await dbContext.SaveChangesAsync();
_searchTargets.SearchTargetsChanged();

3
ErsatzTV.Application/Watermarks/Mapper.cs

@ -21,5 +21,6 @@ internal static class Mapper @@ -21,5 +21,6 @@ internal static class Mapper
watermark.DurationSeconds,
watermark.Opacity,
watermark.PlaceWithinSourceContent,
watermark.OpacityExpression);
watermark.OpacityExpression,
watermark.ZIndex);
}

4
ErsatzTV.Application/Watermarks/WatermarkViewModel.cs

@ -19,5 +19,5 @@ public record WatermarkViewModel( @@ -19,5 +19,5 @@ public record WatermarkViewModel(
int DurationSeconds,
int Opacity,
bool PlaceWithinSourceContent,
string OpacityExpression
);
string OpacityExpression,
int ZIndex);

1
ErsatzTV.Core/Domain/ChannelWatermark.cs

@ -22,6 +22,7 @@ public class ChannelWatermark @@ -22,6 +22,7 @@ public class ChannelWatermark
public string OpacityExpression { get; set; }
public List<PlayoutItem> PlayoutItems { get; set; }
public List<PlayoutItemWatermark> PlayoutItemWatermarks { get; set; }
public int ZIndex { get; set; }
}
public enum ChannelWatermarkMode

2
ErsatzTV.Core/Interfaces/Streaming/IGraphicsElement.cs

@ -4,6 +4,8 @@ namespace ErsatzTV.Core.Interfaces.Streaming; @@ -4,6 +4,8 @@ namespace ErsatzTV.Core.Interfaces.Streaming;
public interface IGraphicsElement
{
int ZIndex { get; }
bool IsFailed { get; set; }
Task InitializeAsync(Resolution frameSize, int frameRate, CancellationToken cancellationToken);

6214
ErsatzTV.Infrastructure.MySql/Migrations/20250808002108_Add_ChannelWatermarkZIndex.Designer.cs generated

File diff suppressed because it is too large Load Diff

29
ErsatzTV.Infrastructure.MySql/Migrations/20250808002108_Add_ChannelWatermarkZIndex.cs

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Add_ChannelWatermarkZIndex : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "ZIndex",
table: "ChannelWatermark",
type: "int",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ZIndex",
table: "ChannelWatermark");
}
}
}

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

@ -397,6 +397,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -397,6 +397,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<double>("WidthPercent")
.HasColumnType("double");
b.Property<int>("ZIndex")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("ChannelWatermark", (string)null);

6051
ErsatzTV.Infrastructure.Sqlite/Migrations/20250808002026_Add_ChannelWatermarkZIndex.Designer.cs generated

File diff suppressed because it is too large Load Diff

29
ErsatzTV.Infrastructure.Sqlite/Migrations/20250808002026_Add_ChannelWatermarkZIndex.cs

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Add_ChannelWatermarkZIndex : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "ZIndex",
table: "ChannelWatermark",
type: "INTEGER",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ZIndex",
table: "ChannelWatermark");
}
}
}

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

@ -382,6 +382,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -382,6 +382,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<double>("WidthPercent")
.HasColumnType("REAL");
b.Property<int>("ZIndex")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("ChannelWatermark", (string)null);

2
ErsatzTV.Infrastructure/Streaming/GraphicsEngine.cs

@ -61,7 +61,7 @@ public class GraphicsEngine(ILogger<GraphicsEngine> logger) : IGraphicsEngine @@ -61,7 +61,7 @@ public class GraphicsEngine(ILogger<GraphicsEngine> logger) : IGraphicsEngine
// draw each element
outputFrame.Mutate(ctx =>
{
foreach (var element in elements)
foreach (var element in elements.OrderBy(e => e.ZIndex))
{
try
{

3
ErsatzTV.Infrastructure/Streaming/WatermarkElement.cs

@ -35,11 +35,14 @@ public class WatermarkElement : IGraphicsElement, IDisposable @@ -35,11 +35,14 @@ public class WatermarkElement : IGraphicsElement, IDisposable
foreach (var watermark in watermarkOptions.Watermark)
{
_watermark = watermark;
ZIndex = watermark.ZIndex;
}
}
public bool IsValid => _imagePath != null && _watermark != null;
public int ZIndex { get; }
public bool IsFailed { get; set; }
public async Task InitializeAsync(Resolution frameSize, int frameRate, CancellationToken cancellationToken)

9
ErsatzTV/Pages/WatermarkEditor.razor

@ -188,6 +188,12 @@ @@ -188,6 +188,12 @@
For="@(() => _model.OpacityExpression)"
Disabled="@(_model.Mode != ChannelWatermarkMode.OpacityExpression)"/>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Z-Index</MudText>
</div>
<MudTextField @bind-Value="_model.ZIndex" For="@(() => _model.ZIndex)"/>
</MudStack>
</MudContainer>
</div>
</MudForm>
@ -233,7 +239,8 @@ @@ -233,7 +239,8 @@
DurationSeconds = 15,
Opacity = 100,
PlaceWithinSourceContent = false,
OpacityExpression = string.Empty
OpacityExpression = string.Empty,
ZIndex = 0
};
}
}

8
ErsatzTV/ViewModels/WatermarkEditViewModel.cs

@ -28,6 +28,7 @@ public class WatermarkEditViewModel @@ -28,6 +28,7 @@ public class WatermarkEditViewModel
Opacity = vm.Opacity;
PlaceWithinSourceContent = vm.PlaceWithinSourceContent;
OpacityExpression = vm.OpacityExpression;
ZIndex = vm.ZIndex;
}
public int Id { get; set; }
@ -45,6 +46,7 @@ public class WatermarkEditViewModel @@ -45,6 +46,7 @@ public class WatermarkEditViewModel
public int Opacity { get; set; }
public bool PlaceWithinSourceContent { get; set; }
public string OpacityExpression { get; set; }
public int ZIndex { get; set; }
public CreateWatermark ToCreate() =>
new(
@ -61,7 +63,8 @@ public class WatermarkEditViewModel @@ -61,7 +63,8 @@ public class WatermarkEditViewModel
DurationSeconds,
Opacity,
PlaceWithinSourceContent,
OpacityExpression);
OpacityExpression,
ZIndex);
public UpdateWatermark ToUpdate() =>
new(
@ -79,5 +82,6 @@ public class WatermarkEditViewModel @@ -79,5 +82,6 @@ public class WatermarkEditViewModel
DurationSeconds,
Opacity,
PlaceWithinSourceContent,
OpacityExpression);
OpacityExpression,
ZIndex);
}

Loading…
Cancel
Save