Browse Source

allow selecting multiple watermarks on schedule items (#2286)

* add and populate new table

* add watermark multiselect

* remove old column

* update changelog

* fix tests
pull/2288/head
Jason Dove 1 week ago committed by GitHub
parent
commit
942cf9e225
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 6
      ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs
  3. 1
      ErsatzTV.Application/ProgramSchedules/Commands/AddProgramScheduleItem.cs
  4. 1
      ErsatzTV.Application/ProgramSchedules/Commands/IProgramScheduleItemRequest.cs
  5. 23
      ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs
  6. 1
      ErsatzTV.Application/ProgramSchedules/Commands/ReplaceProgramScheduleItems.cs
  7. 16
      ErsatzTV.Application/ProgramSchedules/Mapper.cs
  8. 4
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemDurationViewModel.cs
  9. 4
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemFloodViewModel.cs
  10. 4
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs
  11. 4
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemOneViewModel.cs
  12. 2
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemViewModel.cs
  13. 12
      ErsatzTV.Application/ProgramSchedules/Queries/GetProgramScheduleItemsHandler.cs
  14. 2
      ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs
  15. 2
      ErsatzTV.Core/Domain/ChannelWatermark.cs
  16. 3
      ErsatzTV.Core/Domain/PlayoutItem.cs
  17. 4
      ErsatzTV.Core/Domain/ProgramScheduleItem.cs
  18. 9
      ErsatzTV.Core/Domain/ProgramScheduleItemWatermark.cs
  19. 13
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs
  20. 13
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs
  21. 13
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs
  22. 13
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerOne.cs
  23. 6318
      ErsatzTV.Infrastructure.MySql/Migrations/20250809125941_Add_ProgramScheduleItemWatermarks.Designer.cs
  24. 51
      ErsatzTV.Infrastructure.MySql/Migrations/20250809125941_Add_ProgramScheduleItemWatermarks.cs
  25. 6318
      ErsatzTV.Infrastructure.MySql/Migrations/20250809130844_Populate_ProgramScheduleItemWatermarks.Designer.cs
  26. 23
      ErsatzTV.Infrastructure.MySql/Migrations/20250809130844_Populate_ProgramScheduleItemWatermarks.cs
  27. 6306
      ErsatzTV.Infrastructure.MySql/Migrations/20250809133650_Remove_ProgramScheduleItemWatermark.Designer.cs
  28. 49
      ErsatzTV.Infrastructure.MySql/Migrations/20250809133650_Remove_ProgramScheduleItemWatermark.cs
  29. 49
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  30. 6153
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250809130350_Add_ProgramScheduleItemWatermarks.Designer.cs
  31. 50
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250809130350_Add_ProgramScheduleItemWatermarks.cs
  32. 6153
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250809130815_Populate_ProgramScheduleItemWatermarks.Designer.cs
  33. 23
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250809130815_Populate_ProgramScheduleItemWatermarks.cs
  34. 6141
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250809133712_Remove_ProgramScheduleItemWatermark.Designer.cs
  35. 49
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250809133712_Remove_ProgramScheduleItemWatermark.cs
  36. 49
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  37. 17
      ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemConfiguration.cs
  38. 2
      ErsatzTV/Pages/Artist.razor
  39. 29
      ErsatzTV/Pages/ScheduleItemsEditor.razor
  40. 2
      ErsatzTV/Pages/TelevisionEpisodeList.razor
  41. 2
      ErsatzTV/Pages/TelevisionSeasonList.razor
  42. 1
      ErsatzTV/ViewModels/ProgramScheduleItemEditViewModel.cs

1
CHANGELOG.md

@ -38,6 +38,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -38,6 +38,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed
- Allow multiple watermarks on a single playout item
- Allow multiple watermarks in playback troubleshooting
- Classic schedules: allow selecting multiple watermarks on schedule items
- 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

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

@ -148,7 +148,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -148,7 +148,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
{
// copy playout item ids back to watermarks
var allWatermarks = result.AddedItems.SelectMany(item =>
item.PlayoutItemWatermarks.Select(watermark =>
(item.PlayoutItemWatermarks ?? []).Select(watermark =>
{
watermark.PlayoutItemId = item.Id;
watermark.PlayoutItem = null;
@ -163,7 +163,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -163,7 +163,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
{
// copy playout item ids back to graphics elements
var allGraphicsElements = result.AddedItems.SelectMany(item =>
item.PlayoutItemGraphicsElements.Select(graphicsElement =>
(item.PlayoutItemGraphicsElements ?? []).Select(graphicsElement =>
{
graphicsElement.PlayoutItemId = item.Id;
graphicsElement.PlayoutItem = null;
@ -333,6 +333,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -333,6 +333,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
.AsNoTracking()
.Where(ps => ps.Playouts.Any(p => p.Id == playoutId))
.Include(ps => ps.Items)
.ThenInclude(psi => psi.ProgramScheduleItemWatermarks)
.ThenInclude(psi => psi.Watermark)
.Include(ps => ps.Items)
.ThenInclude(psi => psi.Collection)
@ -355,6 +356,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -355,6 +356,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
.Where(pt => pt.PlayoutId == playoutId)
.Include(a => a.ProgramSchedule)
.ThenInclude(ps => ps.Items)
.ThenInclude(psi => psi.ProgramScheduleItemWatermarks)
.ThenInclude(psi => psi.Watermark)
.Include(a => a.ProgramSchedule)
.ThenInclude(ps => ps.Items)

1
ErsatzTV.Application/ProgramSchedules/Commands/AddProgramScheduleItem.cs

@ -31,6 +31,7 @@ public record AddProgramScheduleItem( @@ -31,6 +31,7 @@ public record AddProgramScheduleItem(
int? TailFillerId,
int? FallbackFillerId,
int? WatermarkId,
List<int> WatermarkIds,
string PreferredAudioLanguageCode,
string PreferredAudioTitle,
string PreferredSubtitleLanguageCode,

1
ErsatzTV.Application/ProgramSchedules/Commands/IProgramScheduleItemRequest.cs

@ -29,6 +29,7 @@ public interface IProgramScheduleItemRequest @@ -29,6 +29,7 @@ public interface IProgramScheduleItemRequest
int? TailFillerId { get; }
int? FallbackFillerId { get; }
int? WatermarkId { get; }
List<int> WatermarkIds { get; }
string PreferredAudioLanguageCode { get; }
string PreferredAudioTitle { get; }
string PreferredSubtitleLanguageCode { get; }

23
ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs

@ -184,8 +184,9 @@ public abstract class ProgramScheduleItemCommandBase @@ -184,8 +184,9 @@ public abstract class ProgramScheduleItemCommandBase
protected static ProgramScheduleItem BuildItem(
ProgramSchedule programSchedule,
int index,
IProgramScheduleItemRequest item) =>
item.PlayoutMode switch
IProgramScheduleItemRequest item)
{
ProgramScheduleItem result = item.PlayoutMode switch
{
PlayoutMode.Flood => new ProgramScheduleItemFlood
{
@ -208,7 +209,6 @@ public abstract class ProgramScheduleItemCommandBase @@ -208,7 +209,6 @@ public abstract class ProgramScheduleItemCommandBase
PostRollFillerId = item.PostRollFillerId,
TailFillerId = item.TailFillerId,
FallbackFillerId = item.FallbackFillerId,
WatermarkId = item.WatermarkId,
PreferredAudioLanguageCode = item.PreferredAudioLanguageCode,
PreferredAudioTitle = item.PreferredAudioTitle,
PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode,
@ -235,7 +235,6 @@ public abstract class ProgramScheduleItemCommandBase @@ -235,7 +235,6 @@ public abstract class ProgramScheduleItemCommandBase
PostRollFillerId = item.PostRollFillerId,
TailFillerId = item.TailFillerId,
FallbackFillerId = item.FallbackFillerId,
WatermarkId = item.WatermarkId,
PreferredAudioLanguageCode = item.PreferredAudioLanguageCode,
PreferredAudioTitle = item.PreferredAudioTitle,
PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode,
@ -264,7 +263,6 @@ public abstract class ProgramScheduleItemCommandBase @@ -264,7 +263,6 @@ public abstract class ProgramScheduleItemCommandBase
PostRollFillerId = item.PostRollFillerId,
TailFillerId = item.TailFillerId,
FallbackFillerId = item.FallbackFillerId,
WatermarkId = item.WatermarkId,
PreferredAudioLanguageCode = item.PreferredAudioLanguageCode,
PreferredAudioTitle = item.PreferredAudioTitle,
PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode,
@ -296,7 +294,6 @@ public abstract class ProgramScheduleItemCommandBase @@ -296,7 +294,6 @@ public abstract class ProgramScheduleItemCommandBase
PostRollFillerId = item.PostRollFillerId,
TailFillerId = item.TailFillerId,
FallbackFillerId = item.FallbackFillerId,
WatermarkId = item.WatermarkId,
PreferredAudioLanguageCode = item.PreferredAudioLanguageCode,
PreferredAudioTitle = item.PreferredAudioTitle,
PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode,
@ -305,6 +302,20 @@ public abstract class ProgramScheduleItemCommandBase @@ -305,6 +302,20 @@ public abstract class ProgramScheduleItemCommandBase
_ => throw new NotSupportedException($"Unsupported playout mode {item.PlayoutMode}")
};
foreach (var watermarkId in item.WatermarkIds)
{
result.ProgramScheduleItemWatermarks ??= [];
result.ProgramScheduleItemWatermarks.Add(
new ProgramScheduleItemWatermark
{
ProgramScheduleItem = result,
WatermarkId = watermarkId
});
}
return result;
}
private static TimeSpan? FixStartTime(TimeSpan? startTime) =>
startTime.HasValue && startTime.Value >= TimeSpan.FromDays(1)
? startTime.Value.Subtract(TimeSpan.FromDays(1))

1
ErsatzTV.Application/ProgramSchedules/Commands/ReplaceProgramScheduleItems.cs

@ -31,6 +31,7 @@ public record ReplaceProgramScheduleItem( @@ -31,6 +31,7 @@ public record ReplaceProgramScheduleItem(
int? TailFillerId,
int? FallbackFillerId,
int? WatermarkId,
List<int> WatermarkIds,
string PreferredAudioLanguageCode,
string PreferredAudioTitle,
string PreferredSubtitleLanguageCode,

16
ErsatzTV.Application/ProgramSchedules/Mapper.cs

@ -66,9 +66,7 @@ internal static class Mapper @@ -66,9 +66,7 @@ internal static class Mapper
duration.FallbackFiller != null
? Filler.Mapper.ProjectToViewModel(duration.FallbackFiller)
: null,
duration.Watermark != null
? Watermarks.Mapper.ProjectToViewModel(duration.Watermark)
: null,
duration.ProgramScheduleItemWatermarks.Map(wm => Watermarks.Mapper.ProjectToViewModel(wm.Watermark)).ToList(),
duration.PreferredAudioLanguageCode,
duration.PreferredAudioTitle,
duration.PreferredSubtitleLanguageCode,
@ -119,9 +117,7 @@ internal static class Mapper @@ -119,9 +117,7 @@ internal static class Mapper
flood.FallbackFiller != null
? Filler.Mapper.ProjectToViewModel(flood.FallbackFiller)
: null,
flood.Watermark != null
? Watermarks.Mapper.ProjectToViewModel(flood.Watermark)
: null,
flood.ProgramScheduleItemWatermarks.Map(wm => Watermarks.Mapper.ProjectToViewModel(wm.Watermark)).ToList(),
flood.PreferredAudioLanguageCode,
flood.PreferredAudioTitle,
flood.PreferredSubtitleLanguageCode,
@ -174,9 +170,7 @@ internal static class Mapper @@ -174,9 +170,7 @@ internal static class Mapper
multiple.FallbackFiller != null
? Filler.Mapper.ProjectToViewModel(multiple.FallbackFiller)
: null,
multiple.Watermark != null
? Watermarks.Mapper.ProjectToViewModel(multiple.Watermark)
: null,
multiple.ProgramScheduleItemWatermarks.Map(wm => Watermarks.Mapper.ProjectToViewModel(wm.Watermark)).ToList(),
multiple.PreferredAudioLanguageCode,
multiple.PreferredAudioTitle,
multiple.PreferredSubtitleLanguageCode,
@ -227,9 +221,7 @@ internal static class Mapper @@ -227,9 +221,7 @@ internal static class Mapper
one.FallbackFiller != null
? Filler.Mapper.ProjectToViewModel(one.FallbackFiller)
: null,
one.Watermark != null
? Watermarks.Mapper.ProjectToViewModel(one.Watermark)
: null,
one.ProgramScheduleItemWatermarks.Map(wm => Watermarks.Mapper.ProjectToViewModel(wm.Watermark)).ToList(),
one.PreferredAudioLanguageCode,
one.PreferredAudioTitle,
one.PreferredSubtitleLanguageCode,

4
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemDurationViewModel.cs

@ -33,7 +33,7 @@ public record ProgramScheduleItemDurationViewModel : ProgramScheduleItemViewMode @@ -33,7 +33,7 @@ public record ProgramScheduleItemDurationViewModel : ProgramScheduleItemViewMode
FillerPresetViewModel postRollFiller,
FillerPresetViewModel tailFiller,
FillerPresetViewModel fallbackFiller,
WatermarkViewModel watermark,
List<WatermarkViewModel> watermarks,
string preferredAudioLanguageCode,
string preferredAudioTitle,
string preferredSubtitleLanguageCode,
@ -59,7 +59,7 @@ public record ProgramScheduleItemDurationViewModel : ProgramScheduleItemViewMode @@ -59,7 +59,7 @@ public record ProgramScheduleItemDurationViewModel : ProgramScheduleItemViewMode
postRollFiller,
tailFiller,
fallbackFiller,
watermark,
watermarks,
preferredAudioLanguageCode,
preferredAudioTitle,
preferredSubtitleLanguageCode,

4
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemFloodViewModel.cs

@ -30,7 +30,7 @@ public record ProgramScheduleItemFloodViewModel : ProgramScheduleItemViewModel @@ -30,7 +30,7 @@ public record ProgramScheduleItemFloodViewModel : ProgramScheduleItemViewModel
FillerPresetViewModel postRollFiller,
FillerPresetViewModel tailFiller,
FillerPresetViewModel fallbackFiller,
WatermarkViewModel watermark,
List<WatermarkViewModel> watermarks,
string preferredAudioLanguageCode,
string preferredAudioTitle,
string preferredSubtitleLanguageCode,
@ -56,7 +56,7 @@ public record ProgramScheduleItemFloodViewModel : ProgramScheduleItemViewModel @@ -56,7 +56,7 @@ public record ProgramScheduleItemFloodViewModel : ProgramScheduleItemViewModel
postRollFiller,
tailFiller,
fallbackFiller,
watermark,
watermarks,
preferredAudioLanguageCode,
preferredAudioTitle,
preferredSubtitleLanguageCode,

4
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs

@ -32,7 +32,7 @@ public record ProgramScheduleItemMultipleViewModel : ProgramScheduleItemViewMode @@ -32,7 +32,7 @@ public record ProgramScheduleItemMultipleViewModel : ProgramScheduleItemViewMode
FillerPresetViewModel postRollFiller,
FillerPresetViewModel tailFiller,
FillerPresetViewModel fallbackFiller,
WatermarkViewModel watermark,
List<WatermarkViewModel> watermarks,
string preferredAudioLanguageCode,
string preferredAudioTitle,
string preferredSubtitleLanguageCode,
@ -58,7 +58,7 @@ public record ProgramScheduleItemMultipleViewModel : ProgramScheduleItemViewMode @@ -58,7 +58,7 @@ public record ProgramScheduleItemMultipleViewModel : ProgramScheduleItemViewMode
postRollFiller,
tailFiller,
fallbackFiller,
watermark,
watermarks,
preferredAudioLanguageCode,
preferredAudioTitle,
preferredSubtitleLanguageCode,

4
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemOneViewModel.cs

@ -30,7 +30,7 @@ public record ProgramScheduleItemOneViewModel : ProgramScheduleItemViewModel @@ -30,7 +30,7 @@ public record ProgramScheduleItemOneViewModel : ProgramScheduleItemViewModel
FillerPresetViewModel postRollFiller,
FillerPresetViewModel tailFiller,
FillerPresetViewModel fallbackFiller,
WatermarkViewModel watermark,
List<WatermarkViewModel> watermarks,
string preferredAudioLanguageCode,
string preferredAudioTitle,
string preferredSubtitleLanguageCode,
@ -56,7 +56,7 @@ public record ProgramScheduleItemOneViewModel : ProgramScheduleItemViewModel @@ -56,7 +56,7 @@ public record ProgramScheduleItemOneViewModel : ProgramScheduleItemViewModel
postRollFiller,
tailFiller,
fallbackFiller,
watermark,
watermarks,
preferredAudioLanguageCode,
preferredAudioTitle,
preferredSubtitleLanguageCode,

2
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemViewModel.cs

@ -29,7 +29,7 @@ public abstract record ProgramScheduleItemViewModel( @@ -29,7 +29,7 @@ public abstract record ProgramScheduleItemViewModel(
FillerPresetViewModel PostRollFiller,
FillerPresetViewModel TailFiller,
FillerPresetViewModel FallbackFiller,
WatermarkViewModel Watermark,
List<WatermarkViewModel> Watermarks,
string PreferredAudioLanguageCode,
string PreferredAudioTitle,
string PreferredSubtitleLanguageCode,

12
ErsatzTV.Application/ProgramSchedules/Queries/GetProgramScheduleItemsHandler.cs

@ -6,19 +6,14 @@ using static ErsatzTV.Application.ProgramSchedules.Mapper; @@ -6,19 +6,14 @@ using static ErsatzTV.Application.ProgramSchedules.Mapper;
namespace ErsatzTV.Application.ProgramSchedules;
public class GetProgramScheduleItemsHandler :
public class GetProgramScheduleItemsHandler(IDbContextFactory<TvContext> dbContextFactory) :
IRequestHandler<GetProgramScheduleItems, List<ProgramScheduleItemViewModel>>
{
private readonly IDbContextFactory<TvContext> _dbContextFactory;
public GetProgramScheduleItemsHandler(IDbContextFactory<TvContext> dbContextFactory) =>
_dbContextFactory = dbContextFactory;
public async Task<List<ProgramScheduleItemViewModel>> Handle(
GetProgramScheduleItems request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
Option<ProgramSchedule> maybeProgramSchedule =
await dbContext.ProgramSchedules.SelectOneAsync(ps => ps.Id, ps => ps.Id == request.Id);
@ -50,7 +45,8 @@ public class GetProgramScheduleItemsHandler : @@ -50,7 +45,8 @@ public class GetProgramScheduleItemsHandler :
.Include(i => i.PostRollFiller)
.Include(i => i.TailFiller)
.Include(i => i.FallbackFiller)
.Include(i => i.Watermark)
.Include(i => i.ProgramScheduleItemWatermarks)
.ThenInclude(i => i.Watermark)
.ToListAsync(cancellationToken)
.Map(programScheduleItems => programScheduleItems.Map(ProjectToViewModel)
.Map(psi => EnforceProperties(maybeProgramSchedule, psi)).ToList());

2
ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs

@ -414,6 +414,7 @@ public class ScheduleIntegrationTests @@ -414,6 +414,7 @@ public class ScheduleIntegrationTests
.AsNoTracking()
.Where(ps => ps.Playouts.Any(p => p.Id == playoutId))
.Include(ps => ps.Items)
.ThenInclude(psi => psi.ProgramScheduleItemWatermarks)
.ThenInclude(psi => psi.Watermark)
.Include(ps => ps.Items)
.ThenInclude(psi => psi.Collection)
@ -436,6 +437,7 @@ public class ScheduleIntegrationTests @@ -436,6 +437,7 @@ public class ScheduleIntegrationTests
.Where(pt => pt.PlayoutId == playoutId)
.Include(a => a.ProgramSchedule)
.ThenInclude(ps => ps.Items)
.ThenInclude(psi => psi.ProgramScheduleItemWatermarks)
.ThenInclude(psi => psi.Watermark)
.Include(a => a.ProgramSchedule)
.ThenInclude(ps => ps.Items)

2
ErsatzTV.Core/Domain/ChannelWatermark.cs

@ -22,6 +22,8 @@ public class ChannelWatermark @@ -22,6 +22,8 @@ public class ChannelWatermark
public string OpacityExpression { get; set; }
public List<PlayoutItem> PlayoutItems { get; set; }
public List<PlayoutItemWatermark> PlayoutItemWatermarks { get; set; }
public List<ProgramScheduleItem> ProgramScheduleItems { get; set; }
public List<ProgramScheduleItemWatermark> ProgramScheduleItemWatermarks { get; set; }
public int ZIndex { get; set; }
}

3
ErsatzTV.Core/Domain/PlayoutItem.cs

@ -65,7 +65,8 @@ public class PlayoutItem @@ -65,7 +65,8 @@ public class PlayoutItem
SubtitleMode = SubtitleMode,
BlockKey = BlockKey,
CollectionKey = CollectionKey,
CollectionEtag = CollectionEtag
CollectionEtag = CollectionEtag,
PlayoutItemWatermarks = PlayoutItemWatermarks?.ToList()
};
public string GetDisplayDuration()

4
ErsatzTV.Core/Domain/ProgramScheduleItem.cs

@ -41,8 +41,8 @@ public abstract class ProgramScheduleItem @@ -41,8 +41,8 @@ public abstract class ProgramScheduleItem
public FillerPreset TailFiller { get; set; }
public int? FallbackFillerId { get; set; }
public FillerPreset FallbackFiller { get; set; }
public ChannelWatermark Watermark { get; set; }
public int? WatermarkId { get; set; }
public List<ChannelWatermark> Watermarks { get; set; }
public List<ProgramScheduleItemWatermark> ProgramScheduleItemWatermarks { get; set; }
public string PreferredAudioLanguageCode { get; set; }
public string PreferredAudioTitle { get; set; }
public string PreferredSubtitleLanguageCode { get; set; }

9
ErsatzTV.Core/Domain/ProgramScheduleItemWatermark.cs

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
namespace ErsatzTV.Core.Domain;
public class ProgramScheduleItemWatermark
{
public int ProgramScheduleItemId { get; set; }
public ProgramScheduleItem ProgramScheduleItem { get; set; }
public int? WatermarkId { get; set; }
public ChannelWatermark Watermark { get; set; }
}

13
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs

@ -161,13 +161,18 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche @@ -161,13 +161,18 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche
PreferredAudioLanguageCode = scheduleItem.PreferredAudioLanguageCode,
PreferredAudioTitle = scheduleItem.PreferredAudioTitle,
PreferredSubtitleLanguageCode = scheduleItem.PreferredSubtitleLanguageCode,
SubtitleMode = scheduleItem.SubtitleMode
SubtitleMode = scheduleItem.SubtitleMode,
PlayoutItemWatermarks = []
};
if (scheduleItem.WatermarkId is not null)
foreach (var programScheduleItemWatermark in scheduleItem.ProgramScheduleItemWatermarks ?? [])
{
playoutItem.Watermarks ??= [];
playoutItem.Watermarks.Add(scheduleItem.Watermark);
playoutItem.PlayoutItemWatermarks.Add(
new PlayoutItemWatermark
{
PlayoutItem = playoutItem,
WatermarkId = programScheduleItemWatermark.WatermarkId
});
}
durationUntil.Do(du => playoutItem.GuideFinish = du.UtcDateTime);

13
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs

@ -80,13 +80,18 @@ public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase<ProgramSchedul @@ -80,13 +80,18 @@ public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase<ProgramSchedul
PreferredAudioLanguageCode = scheduleItem.PreferredAudioLanguageCode,
PreferredAudioTitle = scheduleItem.PreferredAudioTitle,
PreferredSubtitleLanguageCode = scheduleItem.PreferredSubtitleLanguageCode,
SubtitleMode = scheduleItem.SubtitleMode
SubtitleMode = scheduleItem.SubtitleMode,
PlayoutItemWatermarks = []
};
if (scheduleItem.WatermarkId is not null)
foreach (var programScheduleItemWatermark in scheduleItem.ProgramScheduleItemWatermarks ?? [])
{
playoutItem.Watermarks ??= [];
playoutItem.Watermarks.Add(scheduleItem.Watermark);
playoutItem.PlayoutItemWatermarks.Add(
new PlayoutItemWatermark
{
PlayoutItem = playoutItem,
WatermarkId = programScheduleItemWatermark.WatermarkId
});
}
var enumeratorStates = new Dictionary<CollectionKey, CollectionEnumeratorState>();

13
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs

@ -104,13 +104,18 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche @@ -104,13 +104,18 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche
PreferredAudioLanguageCode = scheduleItem.PreferredAudioLanguageCode,
PreferredAudioTitle = scheduleItem.PreferredAudioTitle,
PreferredSubtitleLanguageCode = scheduleItem.PreferredSubtitleLanguageCode,
SubtitleMode = scheduleItem.SubtitleMode
SubtitleMode = scheduleItem.SubtitleMode,
PlayoutItemWatermarks = []
};
if (scheduleItem.WatermarkId is not null)
foreach (var programScheduleItemWatermark in scheduleItem.ProgramScheduleItemWatermarks ?? [])
{
playoutItem.Watermarks ??= [];
playoutItem.Watermarks.Add(scheduleItem.Watermark);
playoutItem.PlayoutItemWatermarks.Add(
new PlayoutItemWatermark
{
PlayoutItem = playoutItem,
WatermarkId = programScheduleItemWatermark.WatermarkId
});
}
// LogScheduledItem(scheduleItem, mediaItem, itemStartTime);

13
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerOne.cs

@ -51,13 +51,18 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI @@ -51,13 +51,18 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI
PreferredAudioLanguageCode = scheduleItem.PreferredAudioLanguageCode,
PreferredAudioTitle = scheduleItem.PreferredAudioTitle,
PreferredSubtitleLanguageCode = scheduleItem.PreferredSubtitleLanguageCode,
SubtitleMode = scheduleItem.SubtitleMode
SubtitleMode = scheduleItem.SubtitleMode,
PlayoutItemWatermarks = []
};
if (scheduleItem.WatermarkId is not null)
foreach (var programScheduleItemWatermark in scheduleItem.ProgramScheduleItemWatermarks ?? [])
{
playoutItem.Watermarks ??= [];
playoutItem.Watermarks.Add(scheduleItem.Watermark);
playoutItem.PlayoutItemWatermarks.Add(
new PlayoutItemWatermark
{
PlayoutItem = playoutItem,
WatermarkId = programScheduleItemWatermark.WatermarkId
});
}
List<PlayoutItem> playoutItems = AddFiller(

6318
ErsatzTV.Infrastructure.MySql/Migrations/20250809125941_Add_ProgramScheduleItemWatermarks.Designer.cs generated

File diff suppressed because it is too large Load Diff

51
ErsatzTV.Infrastructure.MySql/Migrations/20250809125941_Add_ProgramScheduleItemWatermarks.cs

@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Add_ProgramScheduleItemWatermarks : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ProgramScheduleItemWatermark",
columns: table => new
{
ProgramScheduleItemId = table.Column<int>(type: "int", nullable: false),
WatermarkId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ProgramScheduleItemWatermark", x => new { x.ProgramScheduleItemId, x.WatermarkId });
table.ForeignKey(
name: "FK_ProgramScheduleItemWatermark_ChannelWatermark_WatermarkId",
column: x => x.WatermarkId,
principalTable: "ChannelWatermark",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_ProgramScheduleItemWatermark_ProgramScheduleItem_ProgramSche~",
column: x => x.ProgramScheduleItemId,
principalTable: "ProgramScheduleItem",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_ProgramScheduleItemWatermark_WatermarkId",
table: "ProgramScheduleItemWatermark",
column: "WatermarkId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ProgramScheduleItemWatermark");
}
}
}

6318
ErsatzTV.Infrastructure.MySql/Migrations/20250809130844_Populate_ProgramScheduleItemWatermarks.Designer.cs generated

File diff suppressed because it is too large Load Diff

23
ErsatzTV.Infrastructure.MySql/Migrations/20250809130844_Populate_ProgramScheduleItemWatermarks.cs

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Populate_ProgramScheduleItemWatermarks : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
@"INSERT INTO `ProgramScheduleItemWatermark` (`ProgramScheduleItemId`, `WatermarkId`)
SELECT `Id`, `WatermarkId` FROM `ProgramScheduleItem` WHERE `WatermarkId` IS NOT NULL");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

6306
ErsatzTV.Infrastructure.MySql/Migrations/20250809133650_Remove_ProgramScheduleItemWatermark.Designer.cs generated

File diff suppressed because it is too large Load Diff

49
ErsatzTV.Infrastructure.MySql/Migrations/20250809133650_Remove_ProgramScheduleItemWatermark.cs

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Remove_ProgramScheduleItemWatermark : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_ProgramScheduleItem_ChannelWatermark_WatermarkId",
table: "ProgramScheduleItem");
migrationBuilder.DropIndex(
name: "IX_ProgramScheduleItem_WatermarkId",
table: "ProgramScheduleItem");
migrationBuilder.DropColumn(
name: "WatermarkId",
table: "ProgramScheduleItem");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "WatermarkId",
table: "ProgramScheduleItem",
type: "int",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_ProgramScheduleItem_WatermarkId",
table: "ProgramScheduleItem",
column: "WatermarkId");
migrationBuilder.AddForeignKey(
name: "FK_ProgramScheduleItem_ChannelWatermark_WatermarkId",
table: "ProgramScheduleItem",
column: "WatermarkId",
principalTable: "ChannelWatermark",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
}
}
}

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

@ -2240,9 +2240,6 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -2240,9 +2240,6 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<int?>("TailFillerId")
.HasColumnType("int");
b.Property<int?>("WatermarkId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("CollectionId");
@ -2267,13 +2264,26 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -2267,13 +2264,26 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.HasIndex("TailFillerId");
b.HasIndex("WatermarkId");
b.ToTable("ProgramScheduleItem", (string)null);
b.UseTptMappingStrategy();
});
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemWatermark", b =>
{
b.Property<int>("ProgramScheduleItemId")
.HasColumnType("int");
b.Property<int>("WatermarkId")
.HasColumnType("int");
b.HasKey("ProgramScheduleItemId", "WatermarkId");
b.HasIndex("WatermarkId");
b.ToTable("ProgramScheduleItemWatermark");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.RemoteStreamMetadata", b =>
{
b.Property<int>("Id")
@ -4915,11 +4925,6 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -4915,11 +4925,6 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
.HasForeignKey("TailFillerId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("ErsatzTV.Core.Domain.ChannelWatermark", "Watermark")
.WithMany()
.HasForeignKey("WatermarkId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("Collection");
b.Navigation("FallbackFiller");
@ -4941,6 +4946,23 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -4941,6 +4946,23 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Navigation("SmartCollection");
b.Navigation("TailFiller");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemWatermark", b =>
{
b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", "ProgramScheduleItem")
.WithMany("ProgramScheduleItemWatermarks")
.HasForeignKey("ProgramScheduleItemId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("ErsatzTV.Core.Domain.ChannelWatermark", "Watermark")
.WithMany("ProgramScheduleItemWatermarks")
.HasForeignKey("WatermarkId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ProgramScheduleItem");
b.Navigation("Watermark");
});
@ -5832,6 +5854,8 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -5832,6 +5854,8 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
modelBuilder.Entity("ErsatzTV.Core.Domain.ChannelWatermark", b =>
{
b.Navigation("PlayoutItemWatermarks");
b.Navigation("ProgramScheduleItemWatermarks");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.Collection", b =>
@ -6037,6 +6061,11 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -6037,6 +6061,11 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Navigation("ProgramScheduleAlternates");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b =>
{
b.Navigation("ProgramScheduleItemWatermarks");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.RemoteStreamMetadata", b =>
{
b.Navigation("Actors");

6153
ErsatzTV.Infrastructure.Sqlite/Migrations/20250809130350_Add_ProgramScheduleItemWatermarks.Designer.cs generated

File diff suppressed because it is too large Load Diff

50
ErsatzTV.Infrastructure.Sqlite/Migrations/20250809130350_Add_ProgramScheduleItemWatermarks.cs

@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Add_ProgramScheduleItemWatermarks : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ProgramScheduleItemWatermark",
columns: table => new
{
ProgramScheduleItemId = table.Column<int>(type: "INTEGER", nullable: false),
WatermarkId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ProgramScheduleItemWatermark", x => new { x.ProgramScheduleItemId, x.WatermarkId });
table.ForeignKey(
name: "FK_ProgramScheduleItemWatermark_ChannelWatermark_WatermarkId",
column: x => x.WatermarkId,
principalTable: "ChannelWatermark",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_ProgramScheduleItemWatermark_ProgramScheduleItem_ProgramScheduleItemId",
column: x => x.ProgramScheduleItemId,
principalTable: "ProgramScheduleItem",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_ProgramScheduleItemWatermark_WatermarkId",
table: "ProgramScheduleItemWatermark",
column: "WatermarkId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ProgramScheduleItemWatermark");
}
}
}

6153
ErsatzTV.Infrastructure.Sqlite/Migrations/20250809130815_Populate_ProgramScheduleItemWatermarks.Designer.cs generated

File diff suppressed because it is too large Load Diff

23
ErsatzTV.Infrastructure.Sqlite/Migrations/20250809130815_Populate_ProgramScheduleItemWatermarks.cs

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Populate_ProgramScheduleItemWatermarks : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
@"INSERT INTO `ProgramScheduleItemWatermark` (`ProgramScheduleItemId`, `WatermarkId`)
SELECT `Id`, `WatermarkId` FROM `ProgramScheduleItem` WHERE `WatermarkId` IS NOT NULL");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

6141
ErsatzTV.Infrastructure.Sqlite/Migrations/20250809133712_Remove_ProgramScheduleItemWatermark.Designer.cs generated

File diff suppressed because it is too large Load Diff

49
ErsatzTV.Infrastructure.Sqlite/Migrations/20250809133712_Remove_ProgramScheduleItemWatermark.cs

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Remove_ProgramScheduleItemWatermark : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_ProgramScheduleItem_ChannelWatermark_WatermarkId",
table: "ProgramScheduleItem");
migrationBuilder.DropIndex(
name: "IX_ProgramScheduleItem_WatermarkId",
table: "ProgramScheduleItem");
migrationBuilder.DropColumn(
name: "WatermarkId",
table: "ProgramScheduleItem");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "WatermarkId",
table: "ProgramScheduleItem",
type: "INTEGER",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_ProgramScheduleItem_WatermarkId",
table: "ProgramScheduleItem",
column: "WatermarkId");
migrationBuilder.AddForeignKey(
name: "FK_ProgramScheduleItem_ChannelWatermark_WatermarkId",
table: "ProgramScheduleItem",
column: "WatermarkId",
principalTable: "ChannelWatermark",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
}
}
}

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

@ -2133,9 +2133,6 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -2133,9 +2133,6 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<int?>("TailFillerId")
.HasColumnType("INTEGER");
b.Property<int?>("WatermarkId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("CollectionId");
@ -2160,13 +2157,26 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -2160,13 +2157,26 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.HasIndex("TailFillerId");
b.HasIndex("WatermarkId");
b.ToTable("ProgramScheduleItem", (string)null);
b.UseTptMappingStrategy();
});
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemWatermark", b =>
{
b.Property<int>("ProgramScheduleItemId")
.HasColumnType("INTEGER");
b.Property<int>("WatermarkId")
.HasColumnType("INTEGER");
b.HasKey("ProgramScheduleItemId", "WatermarkId");
b.HasIndex("WatermarkId");
b.ToTable("ProgramScheduleItemWatermark");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.RemoteStreamMetadata", b =>
{
b.Property<int>("Id")
@ -4750,11 +4760,6 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -4750,11 +4760,6 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
.HasForeignKey("TailFillerId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("ErsatzTV.Core.Domain.ChannelWatermark", "Watermark")
.WithMany()
.HasForeignKey("WatermarkId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("Collection");
b.Navigation("FallbackFiller");
@ -4776,6 +4781,23 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -4776,6 +4781,23 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Navigation("SmartCollection");
b.Navigation("TailFiller");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemWatermark", b =>
{
b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", "ProgramScheduleItem")
.WithMany("ProgramScheduleItemWatermarks")
.HasForeignKey("ProgramScheduleItemId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("ErsatzTV.Core.Domain.ChannelWatermark", "Watermark")
.WithMany("ProgramScheduleItemWatermarks")
.HasForeignKey("WatermarkId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ProgramScheduleItem");
b.Navigation("Watermark");
});
@ -5667,6 +5689,8 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -5667,6 +5689,8 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
modelBuilder.Entity("ErsatzTV.Core.Domain.ChannelWatermark", b =>
{
b.Navigation("PlayoutItemWatermarks");
b.Navigation("ProgramScheduleItemWatermarks");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.Collection", b =>
@ -5872,6 +5896,11 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -5872,6 +5896,11 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Navigation("ProgramScheduleAlternates");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b =>
{
b.Navigation("ProgramScheduleItemWatermarks");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.RemoteStreamMetadata", b =>
{
b.Navigation("Actors");

17
ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemConfiguration.cs

@ -70,10 +70,17 @@ public class ProgramScheduleItemConfiguration : IEntityTypeConfiguration<Program @@ -70,10 +70,17 @@ public class ProgramScheduleItemConfiguration : IEntityTypeConfiguration<Program
.OnDelete(DeleteBehavior.SetNull)
.IsRequired(false);
builder.HasOne(i => i.Watermark)
.WithMany()
.HasForeignKey(i => i.WatermarkId)
.OnDelete(DeleteBehavior.SetNull)
.IsRequired(false);
builder.HasMany(c => c.Watermarks)
.WithMany(m => m.ProgramScheduleItems)
.UsingEntity<ProgramScheduleItemWatermark>(
j => j.HasOne(ci => ci.Watermark)
.WithMany(mi => mi.ProgramScheduleItemWatermarks)
.HasForeignKey(ci => ci.WatermarkId)
.OnDelete(DeleteBehavior.Cascade),
j => j.HasOne(ci => ci.ProgramScheduleItem)
.WithMany(c => c.ProgramScheduleItemWatermarks)
.HasForeignKey(ci => ci.ProgramScheduleItemId)
.OnDelete(DeleteBehavior.Cascade),
j => j.HasKey(ci => new { ci.ProgramScheduleItemId, ci.WatermarkId }));
}
}

2
ErsatzTV/Pages/Artist.razor

@ -299,7 +299,7 @@ @@ -299,7 +299,7 @@
DialogResult result = await dialog.Result;
if (result is { Canceled: false, Data: ProgramScheduleViewModel schedule })
{
await Mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, null, PlayoutMode.One, ProgramScheduleItemCollectionType.Artist, null, null, null, ArtistId, null, PlaybackOrder.Shuffle, FillWithGroupMode.None, MultipleMode.Count, null, null, TailMode.None, null, null, GuideMode.Normal, null, null, null, null, null, null, null, null, null, null), _cts.Token);
await Mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, null, PlayoutMode.One, ProgramScheduleItemCollectionType.Artist, null, null, null, ArtistId, null, PlaybackOrder.Shuffle, FillWithGroupMode.None, MultipleMode.Count, null, null, TailMode.None, null, null, GuideMode.Normal, null, null, null, null, null, null, [], null, null, null, null), _cts.Token);
NavigationManager.NavigateTo($"schedules/{schedule.Id}/items");
}
}

29
ErsatzTV/Pages/ScheduleItemsEditor.razor

@ -50,18 +50,18 @@ @@ -50,18 +50,18 @@
}
</div>
<div style="margin-left: auto" class="d-none d-md-flex">
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveChanges" StartIcon="@Icons.Material.Filled.Save">
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@SaveChanges" StartIcon="@Icons.Material.Filled.Save">
Save Schedule Items
</MudButton>
<MudButton Class="ml-3" Variant="Variant.Filled" Color="Color.Default" OnClick="AddScheduleItem" StartIcon="@Icons.Material.Filled.PlaylistAdd">
<MudButton Class="ml-3" Variant="Variant.Filled" Color="Color.Default" OnClick="@AddScheduleItem" StartIcon="@Icons.Material.Filled.PlaylistAdd">
Add Schedule Item
</MudButton>
</div>
<div style="align-items: center; display: flex; margin-left: auto;" class="d-md-none">
<div class="flex-grow-1"></div>
<MudMenu Icon="@Icons.Material.Filled.MoreVert">
<MudMenuItem Icon="@Icons.Material.Filled.Save" Label="Save Schedule Items" OnClick="SaveChanges"/>
<MudMenuItem Icon="@Icons.Material.Filled.PlaylistAdd" Label="Add Schedule Item" OnClick="AddScheduleItem"/>
<MudMenuItem Icon="@Icons.Material.Filled.Save" Label="Save Schedule Items" OnClick="@SaveChanges"/>
<MudMenuItem Icon="@Icons.Material.Filled.PlaylistAdd" Label="Add Schedule Item" OnClick="@AddScheduleItem"/>
</MudMenu>
</div>
</div>
@ -563,6 +563,21 @@ @@ -563,6 +563,21 @@
}
</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>Preferred Audio Language</MudText>
@ -770,11 +785,11 @@ @@ -770,11 +785,11 @@
PostRollFiller = item.PostRollFiller,
TailFiller = item.TailFiller,
FallbackFiller = item.FallbackFiller,
Watermark = item.Watermark,
PreferredAudioLanguageCode = item.PreferredAudioLanguageCode,
PreferredAudioTitle = item.PreferredAudioTitle,
PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode,
SubtitleMode = item.SubtitleMode
SubtitleMode = item.SubtitleMode,
Watermarks = item.Watermarks
};
switch (item)
@ -839,6 +854,7 @@ @@ -839,6 +854,7 @@
TailFiller = item.TailFiller,
FallbackFiller = item.FallbackFiller,
Watermark = item.Watermark,
Watermarks = item.Watermarks.ToList(),
PreferredAudioLanguageCode = item.PreferredAudioLanguageCode,
PreferredAudioTitle = item.PreferredAudioTitle,
PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode,
@ -913,6 +929,7 @@ @@ -913,6 +929,7 @@
item.TailFiller?.Id,
item.FallbackFiller?.Id,
item.Watermark?.Id,
item.Watermarks.Map(wm => wm.Id).ToList(),
item.PreferredAudioLanguageCode,
item.PreferredAudioTitle,
item.PreferredSubtitleLanguageCode,

2
ErsatzTV/Pages/TelevisionEpisodeList.razor

@ -259,7 +259,7 @@ @@ -259,7 +259,7 @@
DialogResult result = await dialog.Result;
if (result is { Canceled: false, Data: ProgramScheduleViewModel schedule })
{
await Mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionSeason, null, null, null, SeasonId, null, PlaybackOrder.Shuffle, FillWithGroupMode.None, MultipleMode.Count, null, null, TailMode.None, null, null, GuideMode.Normal, null, null, null, null, null, null, null, null, null, null), _cts.Token);
await Mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionSeason, null, null, null, SeasonId, null, PlaybackOrder.Shuffle, FillWithGroupMode.None, MultipleMode.Count, null, null, TailMode.None, null, null, GuideMode.Normal, null, null, null, null, null, null, [], null, null, null, null), _cts.Token);
NavigationManager.NavigateTo($"schedules/{schedule.Id}/items");
}
}

2
ErsatzTV/Pages/TelevisionSeasonList.razor

@ -251,7 +251,7 @@ @@ -251,7 +251,7 @@
DialogResult result = await dialog.Result;
if (result is { Canceled: false, Data: ProgramScheduleViewModel schedule })
{
await Mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionShow, null, null, null, ShowId, null, PlaybackOrder.Shuffle, FillWithGroupMode.None, MultipleMode.Count, null, null, TailMode.None, null, null, GuideMode.Normal, null, null, null, null, null, null, null, null, null, null), _cts.Token);
await Mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionShow, null, null, null, ShowId, null, PlaybackOrder.Shuffle, FillWithGroupMode.None, MultipleMode.Count, null, null, TailMode.None, null, null, GuideMode.Normal, null, null, null, null, null, null, [], null, null, null, null), _cts.Token);
NavigationManager.NavigateTo($"schedules/{schedule.Id}/items");
}
}

1
ErsatzTV/ViewModels/ProgramScheduleItemEditViewModel.cs

@ -93,6 +93,7 @@ public class ProgramScheduleItemEditViewModel : INotifyPropertyChanged @@ -93,6 +93,7 @@ public class ProgramScheduleItemEditViewModel : INotifyPropertyChanged
public FillerPresetViewModel TailFiller { get; set; }
public FillerPresetViewModel FallbackFiller { get; set; }
public WatermarkViewModel Watermark { get; set; }
public IEnumerable<WatermarkViewModel> Watermarks { get; set; }
public string PreferredAudioLanguageCode { get; set; }
public string PreferredAudioTitle { get; set; }
public string PreferredSubtitleLanguageCode { get; set; }

Loading…
Cancel
Save