Browse Source

add schedule item watermark setting (#733)

pull/734/head
Jason Dove 4 years ago committed by GitHub
parent
commit
1ee3446589
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      CHANGELOG.md
  2. 3
      ErsatzTV.Application/ProgramSchedules/Commands/AddProgramScheduleItem.cs
  3. 1
      ErsatzTV.Application/ProgramSchedules/Commands/IProgramScheduleItemRequest.cs
  4. 12
      ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs
  5. 3
      ErsatzTV.Application/ProgramSchedules/Commands/ReplaceProgramScheduleItems.cs
  6. 12
      ErsatzTV.Application/ProgramSchedules/Mapper.cs
  7. 7
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemDurationViewModel.cs
  8. 7
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemFloodViewModel.cs
  9. 7
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs
  10. 7
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemOneViewModel.cs
  11. 4
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemViewModel.cs
  12. 1
      ErsatzTV.Application/ProgramSchedules/Queries/GetProgramScheduleItemsHandler.cs
  13. 3
      ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs
  14. 1
      ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs
  15. 2
      ErsatzTV.Core/Domain/PlayoutItem.cs
  16. 2
      ErsatzTV.Core/Domain/ProgramScheduleItem.cs
  17. 4
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  18. 35
      ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs
  19. 2
      ErsatzTV.Core/FFmpeg/SongVideoGenerator.cs
  20. 2
      ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs
  21. 1
      ErsatzTV.Core/Interfaces/FFmpeg/ISongVideoGenerator.cs
  22. 3
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs
  23. 3
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs
  24. 3
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs
  25. 3
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerOne.cs
  26. 6
      ErsatzTV.Infrastructure/Data/Configurations/PlayoutItemConfiguration.cs
  27. 6
      ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemConfiguration.cs
  28. 3903
      ErsatzTV.Infrastructure/Migrations/20220413010317_Add_ProgramScheduleItemWatermark.Designer.cs
  29. 46
      ErsatzTV.Infrastructure/Migrations/20220413010317_Add_ProgramScheduleItemWatermark.cs
  30. 3915
      ErsatzTV.Infrastructure/Migrations/20220413012616_Add_PlayoutItemWatermark.Designer.cs
  31. 46
      ErsatzTV.Infrastructure/Migrations/20220413012616_Add_PlayoutItemWatermark.cs
  32. 26
      ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs
  33. 2
      ErsatzTV/Pages/Artist.razor
  34. 26
      ErsatzTV/Pages/ScheduleItemsEditor.razor
  35. 2
      ErsatzTV/Pages/TelevisionEpisodeList.razor
  36. 2
      ErsatzTV/Pages/TelevisionSeasonList.razor
  37. 2
      ErsatzTV/ViewModels/ProgramScheduleItemEditViewModel.cs

4
CHANGELOG.md

@ -15,6 +15,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- `Subtitle Mode` will further filter subtitle streams based on attributes (forced, default) - `Subtitle Mode` will further filter subtitle streams based on attributes (forced, default)
- If picture-based subtitles are found after filtering, they will be burned into the video stream - If picture-based subtitles are found after filtering, they will be burned into the video stream
- Detect non-zero ffmpeg exit code from `HLS Segmenter` and `MPEG-TS`, log error output and display error output on stream - Detect non-zero ffmpeg exit code from `HLS Segmenter` and `MPEG-TS`, log error output and display error output on stream
- Add `Watermark` setting to schedule items; this allows override the channel watermark. Watermark priority is:
- Schedule Item
- Channel
- Global
### Changed ### Changed
- Remove legacy transcoder logic option; all channels will use the new transcoder logic - Remove legacy transcoder logic option; all channels will use the new transcoder logic

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

@ -23,4 +23,5 @@ public record AddProgramScheduleItem(
int? MidRollFillerId, int? MidRollFillerId,
int? PostRollFillerId, int? PostRollFillerId,
int? TailFillerId, int? TailFillerId,
int? FallbackFillerId) : IRequest<Either<BaseError, ProgramScheduleItemViewModel>>, IProgramScheduleItemRequest; int? FallbackFillerId,
int? WatermarkId) : IRequest<Either<BaseError, ProgramScheduleItemViewModel>>, IProgramScheduleItemRequest;

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

@ -22,4 +22,5 @@ public interface IProgramScheduleItemRequest
int? PostRollFillerId { get; } int? PostRollFillerId { get; }
int? TailFillerId { get; } int? TailFillerId { get; }
int? FallbackFillerId { get; } int? FallbackFillerId { get; }
int? WatermarkId { get; }
} }

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

@ -176,7 +176,8 @@ public abstract class ProgramScheduleItemCommandBase
MidRollFillerId = item.MidRollFillerId, MidRollFillerId = item.MidRollFillerId,
PostRollFillerId = item.PostRollFillerId, PostRollFillerId = item.PostRollFillerId,
TailFillerId = item.TailFillerId, TailFillerId = item.TailFillerId,
FallbackFillerId = item.FallbackFillerId FallbackFillerId = item.FallbackFillerId,
WatermarkId = item.WatermarkId
}, },
PlayoutMode.One => new ProgramScheduleItemOne PlayoutMode.One => new ProgramScheduleItemOne
{ {
@ -195,7 +196,8 @@ public abstract class ProgramScheduleItemCommandBase
MidRollFillerId = item.MidRollFillerId, MidRollFillerId = item.MidRollFillerId,
PostRollFillerId = item.PostRollFillerId, PostRollFillerId = item.PostRollFillerId,
TailFillerId = item.TailFillerId, TailFillerId = item.TailFillerId,
FallbackFillerId = item.FallbackFillerId FallbackFillerId = item.FallbackFillerId,
WatermarkId = item.WatermarkId
}, },
PlayoutMode.Multiple => new ProgramScheduleItemMultiple PlayoutMode.Multiple => new ProgramScheduleItemMultiple
{ {
@ -215,7 +217,8 @@ public abstract class ProgramScheduleItemCommandBase
MidRollFillerId = item.MidRollFillerId, MidRollFillerId = item.MidRollFillerId,
PostRollFillerId = item.PostRollFillerId, PostRollFillerId = item.PostRollFillerId,
TailFillerId = item.TailFillerId, TailFillerId = item.TailFillerId,
FallbackFillerId = item.FallbackFillerId FallbackFillerId = item.FallbackFillerId,
WatermarkId = item.WatermarkId
}, },
PlayoutMode.Duration => new ProgramScheduleItemDuration PlayoutMode.Duration => new ProgramScheduleItemDuration
{ {
@ -236,7 +239,8 @@ public abstract class ProgramScheduleItemCommandBase
MidRollFillerId = item.MidRollFillerId, MidRollFillerId = item.MidRollFillerId,
PostRollFillerId = item.PostRollFillerId, PostRollFillerId = item.PostRollFillerId,
TailFillerId = item.TailFillerId, TailFillerId = item.TailFillerId,
FallbackFillerId = item.FallbackFillerId FallbackFillerId = item.FallbackFillerId,
WatermarkId = item.WatermarkId
}, },
_ => throw new NotSupportedException($"Unsupported playout mode {item.PlayoutMode}") _ => throw new NotSupportedException($"Unsupported playout mode {item.PlayoutMode}")
}; };

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

@ -23,7 +23,8 @@ public record ReplaceProgramScheduleItem(
int? MidRollFillerId, int? MidRollFillerId,
int? PostRollFillerId, int? PostRollFillerId,
int? TailFillerId, int? TailFillerId,
int? FallbackFillerId) : IProgramScheduleItemRequest; int? FallbackFillerId,
int? WatermarkId) : IProgramScheduleItemRequest;
public record ReplaceProgramScheduleItems public record ReplaceProgramScheduleItems
(int ProgramScheduleId, List<ReplaceProgramScheduleItem> Items) : IRequest< (int ProgramScheduleId, List<ReplaceProgramScheduleItem> Items) : IRequest<

12
ErsatzTV.Application/ProgramSchedules/Mapper.cs

@ -57,6 +57,9 @@ internal static class Mapper
: null, : null,
duration.FallbackFiller != null duration.FallbackFiller != null
? Filler.Mapper.ProjectToViewModel(duration.FallbackFiller) ? Filler.Mapper.ProjectToViewModel(duration.FallbackFiller)
: null,
duration.Watermark != null
? Watermarks.Mapper.ProjectToViewModel(duration.Watermark)
: null), : null),
ProgramScheduleItemFlood flood => ProgramScheduleItemFlood flood =>
new ProgramScheduleItemFloodViewModel( new ProgramScheduleItemFloodViewModel(
@ -98,6 +101,9 @@ internal static class Mapper
: null, : null,
flood.FallbackFiller != null flood.FallbackFiller != null
? Filler.Mapper.ProjectToViewModel(flood.FallbackFiller) ? Filler.Mapper.ProjectToViewModel(flood.FallbackFiller)
: null,
flood.Watermark != null
? Watermarks.Mapper.ProjectToViewModel(flood.Watermark)
: null), : null),
ProgramScheduleItemMultiple multiple => ProgramScheduleItemMultiple multiple =>
new ProgramScheduleItemMultipleViewModel( new ProgramScheduleItemMultipleViewModel(
@ -140,6 +146,9 @@ internal static class Mapper
: null, : null,
multiple.FallbackFiller != null multiple.FallbackFiller != null
? Filler.Mapper.ProjectToViewModel(multiple.FallbackFiller) ? Filler.Mapper.ProjectToViewModel(multiple.FallbackFiller)
: null,
multiple.Watermark != null
? Watermarks.Mapper.ProjectToViewModel(multiple.Watermark)
: null), : null),
ProgramScheduleItemOne one => ProgramScheduleItemOne one =>
new ProgramScheduleItemOneViewModel( new ProgramScheduleItemOneViewModel(
@ -181,6 +190,9 @@ internal static class Mapper
: null, : null,
one.FallbackFiller != null one.FallbackFiller != null
? Filler.Mapper.ProjectToViewModel(one.FallbackFiller) ? Filler.Mapper.ProjectToViewModel(one.FallbackFiller)
: null,
one.Watermark != null
? Watermarks.Mapper.ProjectToViewModel(one.Watermark)
: null), : null),
_ => throw new NotSupportedException( _ => throw new NotSupportedException(
$"Unsupported program schedule item type {programScheduleItem.GetType().Name}") $"Unsupported program schedule item type {programScheduleItem.GetType().Name}")

7
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemDurationViewModel.cs

@ -1,6 +1,7 @@
using ErsatzTV.Application.Filler; using ErsatzTV.Application.Filler;
using ErsatzTV.Application.MediaCollections; using ErsatzTV.Application.MediaCollections;
using ErsatzTV.Application.MediaItems; using ErsatzTV.Application.MediaItems;
using ErsatzTV.Application.Watermarks;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.ProgramSchedules; namespace ErsatzTV.Application.ProgramSchedules;
@ -26,7 +27,8 @@ public record ProgramScheduleItemDurationViewModel : ProgramScheduleItemViewMode
FillerPresetViewModel midRollFiller, FillerPresetViewModel midRollFiller,
FillerPresetViewModel postRollFiller, FillerPresetViewModel postRollFiller,
FillerPresetViewModel tailFiller, FillerPresetViewModel tailFiller,
FillerPresetViewModel fallbackFiller) : base( FillerPresetViewModel fallbackFiller,
WatermarkViewModel watermark) : base(
id, id,
index, index,
startType, startType,
@ -44,7 +46,8 @@ public record ProgramScheduleItemDurationViewModel : ProgramScheduleItemViewMode
midRollFiller, midRollFiller,
postRollFiller, postRollFiller,
tailFiller, tailFiller,
fallbackFiller) fallbackFiller,
watermark)
{ {
PlayoutDuration = playoutDuration; PlayoutDuration = playoutDuration;
TailMode = tailMode; TailMode = tailMode;

7
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemFloodViewModel.cs

@ -1,6 +1,7 @@
using ErsatzTV.Application.Filler; using ErsatzTV.Application.Filler;
using ErsatzTV.Application.MediaCollections; using ErsatzTV.Application.MediaCollections;
using ErsatzTV.Application.MediaItems; using ErsatzTV.Application.MediaItems;
using ErsatzTV.Application.Watermarks;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.ProgramSchedules; namespace ErsatzTV.Application.ProgramSchedules;
@ -24,7 +25,8 @@ public record ProgramScheduleItemFloodViewModel : ProgramScheduleItemViewModel
FillerPresetViewModel midRollFiller, FillerPresetViewModel midRollFiller,
FillerPresetViewModel postRollFiller, FillerPresetViewModel postRollFiller,
FillerPresetViewModel tailFiller, FillerPresetViewModel tailFiller,
FillerPresetViewModel fallbackFiller) : base( FillerPresetViewModel fallbackFiller,
WatermarkViewModel watermark) : base(
id, id,
index, index,
startType, startType,
@ -42,7 +44,8 @@ public record ProgramScheduleItemFloodViewModel : ProgramScheduleItemViewModel
midRollFiller, midRollFiller,
postRollFiller, postRollFiller,
tailFiller, tailFiller,
fallbackFiller) fallbackFiller,
watermark)
{ {
} }
} }

7
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs

@ -1,6 +1,7 @@
using ErsatzTV.Application.Filler; using ErsatzTV.Application.Filler;
using ErsatzTV.Application.MediaCollections; using ErsatzTV.Application.MediaCollections;
using ErsatzTV.Application.MediaItems; using ErsatzTV.Application.MediaItems;
using ErsatzTV.Application.Watermarks;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.ProgramSchedules; namespace ErsatzTV.Application.ProgramSchedules;
@ -25,7 +26,8 @@ public record ProgramScheduleItemMultipleViewModel : ProgramScheduleItemViewMode
FillerPresetViewModel midRollFiller, FillerPresetViewModel midRollFiller,
FillerPresetViewModel postRollFiller, FillerPresetViewModel postRollFiller,
FillerPresetViewModel tailFiller, FillerPresetViewModel tailFiller,
FillerPresetViewModel fallbackFiller) : base( FillerPresetViewModel fallbackFiller,
WatermarkViewModel watermark) : base(
id, id,
index, index,
startType, startType,
@ -43,7 +45,8 @@ public record ProgramScheduleItemMultipleViewModel : ProgramScheduleItemViewMode
midRollFiller, midRollFiller,
postRollFiller, postRollFiller,
tailFiller, tailFiller,
fallbackFiller) => fallbackFiller,
watermark) =>
Count = count; Count = count;
public int Count { get; } public int Count { get; }

7
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemOneViewModel.cs

@ -1,6 +1,7 @@
using ErsatzTV.Application.Filler; using ErsatzTV.Application.Filler;
using ErsatzTV.Application.MediaCollections; using ErsatzTV.Application.MediaCollections;
using ErsatzTV.Application.MediaItems; using ErsatzTV.Application.MediaItems;
using ErsatzTV.Application.Watermarks;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.ProgramSchedules; namespace ErsatzTV.Application.ProgramSchedules;
@ -24,7 +25,8 @@ public record ProgramScheduleItemOneViewModel : ProgramScheduleItemViewModel
FillerPresetViewModel midRollFiller, FillerPresetViewModel midRollFiller,
FillerPresetViewModel postRollFiller, FillerPresetViewModel postRollFiller,
FillerPresetViewModel tailFiller, FillerPresetViewModel tailFiller,
FillerPresetViewModel fallbackFiller) : base( FillerPresetViewModel fallbackFiller,
WatermarkViewModel watermark) : base(
id, id,
index, index,
startType, startType,
@ -42,7 +44,8 @@ public record ProgramScheduleItemOneViewModel : ProgramScheduleItemViewModel
midRollFiller, midRollFiller,
postRollFiller, postRollFiller,
tailFiller, tailFiller,
fallbackFiller) fallbackFiller,
watermark)
{ {
} }
} }

4
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemViewModel.cs

@ -1,6 +1,7 @@
using ErsatzTV.Application.Filler; using ErsatzTV.Application.Filler;
using ErsatzTV.Application.MediaCollections; using ErsatzTV.Application.MediaCollections;
using ErsatzTV.Application.MediaItems; using ErsatzTV.Application.MediaItems;
using ErsatzTV.Application.Watermarks;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.ProgramSchedules; namespace ErsatzTV.Application.ProgramSchedules;
@ -23,7 +24,8 @@ public abstract record ProgramScheduleItemViewModel(
FillerPresetViewModel MidRollFiller, FillerPresetViewModel MidRollFiller,
FillerPresetViewModel PostRollFiller, FillerPresetViewModel PostRollFiller,
FillerPresetViewModel TailFiller, FillerPresetViewModel TailFiller,
FillerPresetViewModel FallbackFiller) FillerPresetViewModel FallbackFiller,
WatermarkViewModel Watermark)
{ {
public string Name => CollectionType switch public string Name => CollectionType switch
{ {

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

@ -49,6 +49,7 @@ public class GetProgramScheduleItemsHandler :
.Include(i => i.PostRollFiller) .Include(i => i.PostRollFiller)
.Include(i => i.TailFiller) .Include(i => i.TailFiller)
.Include(i => i.FallbackFiller) .Include(i => i.FallbackFiller)
.Include(i => i.Watermark)
.ToListAsync(cancellationToken) .ToListAsync(cancellationToken)
.Map( .Map(
programScheduleItems => programScheduleItems.Map(ProjectToViewModel) programScheduleItems => programScheduleItems.Map(ProjectToViewModel)

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

@ -97,6 +97,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
.Include(i => i.MediaItem) .Include(i => i.MediaItem)
.ThenInclude(mi => (mi as Song).SongMetadata) .ThenInclude(mi => (mi as Song).SongMetadata)
.ThenInclude(sm => sm.Artwork) .ThenInclude(sm => sm.Artwork)
.Include(i => i.Watermark)
.ForChannelAndTime(channel.Id, now) .ForChannelAndTime(channel.Id, now)
.Map(o => o.ToEither<BaseError>(new UnableToLocatePlayoutItem())) .Map(o => o.ToEither<BaseError>(new UnableToLocatePlayoutItem()))
.BindT(ValidatePlayoutItemPath); .BindT(ValidatePlayoutItemPath);
@ -127,6 +128,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
(videoPath, videoVersion) = await _songVideoGenerator.GenerateSongVideo( (videoPath, videoVersion) = await _songVideoGenerator.GenerateSongVideo(
song, song,
channel, channel,
Optional(playoutItemWithPath.PlayoutItem.Watermark),
maybeGlobalWatermark, maybeGlobalWatermark,
ffmpegPath, ffmpegPath,
ffprobePath, ffprobePath,
@ -149,6 +151,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
playoutItemWithPath.PlayoutItem.StartOffset, playoutItemWithPath.PlayoutItem.StartOffset,
playoutItemWithPath.PlayoutItem.FinishOffset, playoutItemWithPath.PlayoutItem.FinishOffset,
request.StartAtZero ? playoutItemWithPath.PlayoutItem.StartOffset : now, request.StartAtZero ? playoutItemWithPath.PlayoutItem.StartOffset : now,
Optional(playoutItemWithPath.PlayoutItem.Watermark),
maybeGlobalWatermark, maybeGlobalWatermark,
channel.FFmpegProfile.VaapiDriver, channel.FFmpegProfile.VaapiDriver,
channel.FFmpegProfile.VaapiDevice, channel.FFmpegProfile.VaapiDevice,

1
ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs

@ -340,6 +340,7 @@ public class TranscodingTests
now, now,
now + TimeSpan.FromSeconds(5), now + TimeSpan.FromSeconds(5),
now, now,
Option<ChannelWatermark>.None,
channelWatermark, channelWatermark,
VaapiDriver.Default, VaapiDriver.Default,
"/dev/dri/renderD128", "/dev/dri/renderD128",

2
ErsatzTV.Core/Domain/PlayoutItem.cs

@ -20,6 +20,8 @@ public class PlayoutItem
public TimeSpan InPoint { get; set; } public TimeSpan InPoint { get; set; }
public TimeSpan OutPoint { get; set; } public TimeSpan OutPoint { get; set; }
public string ChapterTitle { get; set; } public string ChapterTitle { get; set; }
public ChannelWatermark Watermark { get; set; }
public int? WatermarkId { get; set; }
public DateTimeOffset StartOffset => new DateTimeOffset(Start, TimeSpan.Zero).ToLocalTime(); public DateTimeOffset StartOffset => new DateTimeOffset(Start, TimeSpan.Zero).ToLocalTime();
public DateTimeOffset FinishOffset => new DateTimeOffset(Finish, TimeSpan.Zero).ToLocalTime(); public DateTimeOffset FinishOffset => new DateTimeOffset(Finish, TimeSpan.Zero).ToLocalTime();

2
ErsatzTV.Core/Domain/ProgramScheduleItem.cs

@ -32,4 +32,6 @@ public abstract class ProgramScheduleItem
public FillerPreset TailFiller { get; set; } public FillerPreset TailFiller { get; set; }
public int? FallbackFillerId { get; set; } public int? FallbackFillerId { get; set; }
public FillerPreset FallbackFiller { get; set; } public FillerPreset FallbackFiller { get; set; }
public ChannelWatermark Watermark { get; set; }
public int? WatermarkId { get; set; }
} }

4
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -44,6 +44,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
DateTimeOffset start, DateTimeOffset start,
DateTimeOffset finish, DateTimeOffset finish,
DateTimeOffset now, DateTimeOffset now,
Option<ChannelWatermark> playoutItemWatermark,
Option<ChannelWatermark> globalWatermark, Option<ChannelWatermark> globalWatermark,
VaapiDriver vaapiDriver, VaapiDriver vaapiDriver,
string vaapiDevice, string vaapiDevice,
@ -76,6 +77,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
await _ffmpegProcessService.GetWatermarkOptions( await _ffmpegProcessService.GetWatermarkOptions(
ffprobePath, ffprobePath,
channel, channel,
playoutItemWatermark,
globalWatermark, globalWatermark,
videoVersion, videoVersion,
None, None,
@ -353,6 +355,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
string ffprobePath, string ffprobePath,
Option<string> subtitleFile, Option<string> subtitleFile,
Channel channel, Channel channel,
Option<ChannelWatermark> playoutItemWatermark,
Option<ChannelWatermark> globalWatermark, Option<ChannelWatermark> globalWatermark,
MediaVersion videoVersion, MediaVersion videoVersion,
string videoPath, string videoPath,
@ -368,6 +371,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
ffprobePath, ffprobePath,
subtitleFile, subtitleFile,
channel, channel,
playoutItemWatermark,
globalWatermark, globalWatermark,
videoVersion, videoVersion,
videoPath, videoPath,

35
ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs

@ -164,6 +164,7 @@ public class FFmpegProcessService
string ffprobePath, string ffprobePath,
Option<string> subtitleFile, Option<string> subtitleFile,
Channel channel, Channel channel,
Option<ChannelWatermark> playoutItemWatermark,
Option<ChannelWatermark> globalWatermark, Option<ChannelWatermark> globalWatermark,
MediaVersion videoVersion, MediaVersion videoVersion,
string videoPath, string videoPath,
@ -199,6 +200,7 @@ public class FFmpegProcessService
await GetWatermarkOptions( await GetWatermarkOptions(
ffprobePath, ffprobePath,
channel, channel,
playoutItemWatermark,
globalWatermark, globalWatermark,
videoVersion, videoVersion,
watermarkOverride, watermarkOverride,
@ -273,6 +275,7 @@ public class FFmpegProcessService
internal async Task<WatermarkOptions> GetWatermarkOptions( internal async Task<WatermarkOptions> GetWatermarkOptions(
string ffprobePath, string ffprobePath,
Channel channel, Channel channel,
Option<ChannelWatermark> playoutItemWatermark,
Option<ChannelWatermark> globalWatermark, Option<ChannelWatermark> globalWatermark,
MediaVersion videoVersion, MediaVersion videoVersion,
Option<ChannelWatermark> watermarkOverride, Option<ChannelWatermark> watermarkOverride,
@ -288,6 +291,38 @@ public class FFmpegProcessService
0, 0,
false); false);
} }
// check for playout item watermark
foreach (ChannelWatermark watermark in playoutItemWatermark)
{
switch (watermark.ImageSource)
{
case ChannelWatermarkImageSource.Custom:
string customPath = _imageCache.GetPathForImage(
watermark.Image,
ArtworkKind.Watermark,
Option<int>.None);
return new WatermarkOptions(
await watermarkOverride.IfNoneAsync(watermark),
customPath,
None,
await IsAnimated(ffprobePath, customPath));
case ChannelWatermarkImageSource.ChannelLogo:
Option<string> maybeChannelPath = channel.Artwork
.Filter(a => a.ArtworkKind == ArtworkKind.Logo)
.HeadOrNone()
.Map(a => _imageCache.GetPathForImage(a.Path, ArtworkKind.Logo, Option<int>.None));
return new WatermarkOptions(
await watermarkOverride.IfNoneAsync(watermark),
maybeChannelPath,
None,
await maybeChannelPath.Match(
p => IsAnimated(ffprobePath, p),
() => Task.FromResult(false)));
default:
throw new NotSupportedException("Unsupported watermark image source");
}
}
// check for channel watermark // check for channel watermark
if (channel.Watermark != null) if (channel.Watermark != null)

2
ErsatzTV.Core/FFmpeg/SongVideoGenerator.cs

@ -29,6 +29,7 @@ public class SongVideoGenerator : ISongVideoGenerator
public async Task<Tuple<string, MediaVersion>> GenerateSongVideo( public async Task<Tuple<string, MediaVersion>> GenerateSongVideo(
Song song, Song song,
Channel channel, Channel channel,
Option<ChannelWatermark> maybePlayoutItemWatermark,
Option<ChannelWatermark> maybeGlobalWatermark, Option<ChannelWatermark> maybeGlobalWatermark,
string ffmpegPath, string ffmpegPath,
string ffprobePath, string ffprobePath,
@ -216,6 +217,7 @@ public class SongVideoGenerator : ISongVideoGenerator
ffprobePath, ffprobePath,
subtitleFile, subtitleFile,
channel, channel,
maybePlayoutItemWatermark,
maybeGlobalWatermark, maybeGlobalWatermark,
videoVersion, videoVersion,
videoPath, videoPath,

2
ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs

@ -20,6 +20,7 @@ public interface IFFmpegProcessService
DateTimeOffset start, DateTimeOffset start,
DateTimeOffset finish, DateTimeOffset finish,
DateTimeOffset now, DateTimeOffset now,
Option<ChannelWatermark> playoutItemWatermark,
Option<ChannelWatermark> globalWatermark, Option<ChannelWatermark> globalWatermark,
VaapiDriver vaapiDriver, VaapiDriver vaapiDriver,
string vaapiDevice, string vaapiDevice,
@ -53,6 +54,7 @@ public interface IFFmpegProcessService
string ffprobePath, string ffprobePath,
Option<string> subtitleFile, Option<string> subtitleFile,
Channel channel, Channel channel,
Option<ChannelWatermark> playoutItemWatermark,
Option<ChannelWatermark> globalWatermark, Option<ChannelWatermark> globalWatermark,
MediaVersion videoVersion, MediaVersion videoVersion,
string videoPath, string videoPath,

1
ErsatzTV.Core/Interfaces/FFmpeg/ISongVideoGenerator.cs

@ -7,6 +7,7 @@ public interface ISongVideoGenerator
Task<Tuple<string, MediaVersion>> GenerateSongVideo( Task<Tuple<string, MediaVersion>> GenerateSongVideo(
Song song, Song song,
Channel channel, Channel channel,
Option<ChannelWatermark> maybePlayoutItemWatermark,
Option<ChannelWatermark> maybeGlobalWatermark, Option<ChannelWatermark> maybeGlobalWatermark,
string ffmpegPath, string ffmpegPath,
string ffprobePath, string ffprobePath,

3
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs

@ -72,7 +72,8 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche
FillerKind = scheduleItem.GuideMode == GuideMode.Filler FillerKind = scheduleItem.GuideMode == GuideMode.Filler
? FillerKind.Tail ? FillerKind.Tail
: FillerKind.None, : FillerKind.None,
CustomTitle = scheduleItem.CustomTitle CustomTitle = scheduleItem.CustomTitle,
WatermarkId = scheduleItem.WatermarkId
}; };
durationUntil.Do(du => playoutItem.GuideFinish = du.UtcDateTime); durationUntil.Do(du => playoutItem.GuideFinish = du.UtcDateTime);

3
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs

@ -49,7 +49,8 @@ public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase<ProgramSchedul
GuideGroup = nextState.NextGuideGroup, GuideGroup = nextState.NextGuideGroup,
FillerKind = scheduleItem.GuideMode == GuideMode.Filler FillerKind = scheduleItem.GuideMode == GuideMode.Filler
? FillerKind.Tail ? FillerKind.Tail
: FillerKind.None : FillerKind.None,
WatermarkId = scheduleItem.WatermarkId
}; };
DateTimeOffset peekScheduleItemStart = DateTimeOffset peekScheduleItemStart =

3
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs

@ -61,7 +61,8 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche
GuideGroup = nextState.NextGuideGroup, GuideGroup = nextState.NextGuideGroup,
FillerKind = scheduleItem.GuideMode == GuideMode.Filler FillerKind = scheduleItem.GuideMode == GuideMode.Filler
? FillerKind.Tail ? FillerKind.Tail
: FillerKind.None : FillerKind.None,
WatermarkId = scheduleItem.WatermarkId
}; };
// LogScheduledItem(scheduleItem, mediaItem, itemStartTime); // LogScheduledItem(scheduleItem, mediaItem, itemStartTime);

3
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerOne.cs

@ -40,7 +40,8 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI
GuideGroup = playoutBuilderState.NextGuideGroup, GuideGroup = playoutBuilderState.NextGuideGroup,
FillerKind = scheduleItem.GuideMode == GuideMode.Filler FillerKind = scheduleItem.GuideMode == GuideMode.Filler
? FillerKind.Tail ? FillerKind.Tail
: FillerKind.None : FillerKind.None,
WatermarkId = scheduleItem.WatermarkId
}; };
DateTimeOffset itemEndTimeWithFiller = CalculateEndTimeWithFiller( DateTimeOffset itemEndTimeWithFiller = CalculateEndTimeWithFiller(

6
ErsatzTV.Infrastructure/Data/Configurations/PlayoutItemConfiguration.cs

@ -15,5 +15,11 @@ public class PlayoutItemConfiguration : IEntityTypeConfiguration<PlayoutItem>
.WithMany() .WithMany()
.HasForeignKey(pi => pi.MediaItemId) .HasForeignKey(pi => pi.MediaItemId)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
builder.HasOne(i => i.Watermark)
.WithMany()
.HasForeignKey(i => i.WatermarkId)
.OnDelete(DeleteBehavior.SetNull)
.IsRequired(false);
} }
} }

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

@ -63,5 +63,11 @@ public class ProgramScheduleItemConfiguration : IEntityTypeConfiguration<Program
.HasForeignKey(i => i.FallbackFillerId) .HasForeignKey(i => i.FallbackFillerId)
.OnDelete(DeleteBehavior.SetNull) .OnDelete(DeleteBehavior.SetNull)
.IsRequired(false); .IsRequired(false);
builder.HasOne(i => i.Watermark)
.WithMany()
.HasForeignKey(i => i.WatermarkId)
.OnDelete(DeleteBehavior.SetNull)
.IsRequired(false);
} }
} }

3903
ErsatzTV.Infrastructure/Migrations/20220413010317_Add_ProgramScheduleItemWatermark.Designer.cs generated

File diff suppressed because it is too large Load Diff

46
ErsatzTV.Infrastructure/Migrations/20220413010317_Add_ProgramScheduleItemWatermark.cs

@ -0,0 +1,46 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Add_ProgramScheduleItemWatermark : Migration
{
protected override void Up(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);
}
protected override void Down(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");
}
}
}

3915
ErsatzTV.Infrastructure/Migrations/20220413012616_Add_PlayoutItemWatermark.Designer.cs generated

File diff suppressed because it is too large Load Diff

46
ErsatzTV.Infrastructure/Migrations/20220413012616_Add_PlayoutItemWatermark.cs

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

26
ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs

@ -15,7 +15,7 @@ namespace ErsatzTV.Infrastructure.Migrations
protected override void BuildModel(ModelBuilder modelBuilder) protected override void BuildModel(ModelBuilder modelBuilder)
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.3"); modelBuilder.HasAnnotation("ProductVersion", "6.0.4");
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b => modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
{ {
@ -1331,12 +1331,17 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Property<DateTime>("Start") b.Property<DateTime>("Start")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<int?>("WatermarkId")
.HasColumnType("INTEGER");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("MediaItemId"); b.HasIndex("MediaItemId");
b.HasIndex("PlayoutId"); b.HasIndex("PlayoutId");
b.HasIndex("WatermarkId");
b.ToTable("PlayoutItem", (string)null); b.ToTable("PlayoutItem", (string)null);
}); });
@ -1511,6 +1516,9 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Property<int?>("TailFillerId") b.Property<int?>("TailFillerId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<int?>("WatermarkId")
.HasColumnType("INTEGER");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("CollectionId"); b.HasIndex("CollectionId");
@ -1533,6 +1541,8 @@ namespace ErsatzTV.Infrastructure.Migrations
b.HasIndex("TailFillerId"); b.HasIndex("TailFillerId");
b.HasIndex("WatermarkId");
b.ToTable("ProgramScheduleItem", (string)null); b.ToTable("ProgramScheduleItem", (string)null);
}); });
@ -2941,9 +2951,16 @@ namespace ErsatzTV.Infrastructure.Migrations
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("ErsatzTV.Core.Domain.ChannelWatermark", "Watermark")
.WithMany()
.HasForeignKey("WatermarkId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("MediaItem"); b.Navigation("MediaItem");
b.Navigation("Playout"); b.Navigation("Playout");
b.Navigation("Watermark");
}); });
modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", b => modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", b =>
@ -3088,6 +3105,11 @@ namespace ErsatzTV.Infrastructure.Migrations
.HasForeignKey("TailFillerId") .HasForeignKey("TailFillerId")
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
b.HasOne("ErsatzTV.Core.Domain.ChannelWatermark", "Watermark")
.WithMany()
.HasForeignKey("WatermarkId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("Collection"); b.Navigation("Collection");
b.Navigation("FallbackFiller"); b.Navigation("FallbackFiller");
@ -3107,6 +3129,8 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Navigation("SmartCollection"); b.Navigation("SmartCollection");
b.Navigation("TailFiller"); b.Navigation("TailFiller");
b.Navigation("Watermark");
}); });
modelBuilder.Entity("ErsatzTV.Core.Domain.SeasonMetadata", b => modelBuilder.Entity("ErsatzTV.Core.Domain.SeasonMetadata", b =>

2
ErsatzTV/Pages/Artist.razor

@ -238,7 +238,7 @@
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Cancelled && result.Data is ProgramScheduleViewModel schedule) if (!result.Cancelled && result.Data is ProgramScheduleViewModel schedule)
{ {
await _mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.Artist, null, null, null, ArtistId, PlaybackOrder.Shuffle, null, null, TailMode.None, null, GuideMode.Normal, null, null, null, null, null), _cts.Token); await _mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.Artist, null, null, null, ArtistId, PlaybackOrder.Shuffle, null, null, TailMode.None, null, GuideMode.Normal, null, null, null, null, null, null), _cts.Token);
_navigationManager.NavigateTo($"/schedules/{schedule.Id}/items"); _navigationManager.NavigateTo($"/schedules/{schedule.Id}/items");
} }
} }

26
ErsatzTV/Pages/ScheduleItemsEditor.razor

@ -6,6 +6,7 @@
@using ErsatzTV.Application.Filler @using ErsatzTV.Application.Filler
@using ErsatzTV.Core.Domain.Filler @using ErsatzTV.Core.Domain.Filler
@using ErsatzTV.Application.Artists @using ErsatzTV.Application.Artists
@using ErsatzTV.Application.Watermarks
@implements IDisposable @implements IDisposable
@inject NavigationManager _navigationManager @inject NavigationManager _navigationManager
@inject ILogger<ScheduleItemsEditor> _logger @inject ILogger<ScheduleItemsEditor> _logger
@ -221,6 +222,12 @@
<MudSelectItem Value="@GuideMode.Normal">Normal</MudSelectItem> <MudSelectItem Value="@GuideMode.Normal">Normal</MudSelectItem>
<MudSelectItem Value="@GuideMode.Filler">Filler</MudSelectItem> <MudSelectItem Value="@GuideMode.Filler">Filler</MudSelectItem>
</MudSelect> </MudSelect>
<MudSelect Class="mt-3" Label="Watermark" @bind-Value="@_selectedItem.Watermark" For="@(() => _selectedItem.Watermark)" Clearable="true">
@foreach (WatermarkViewModel watermark in _watermarks)
{
<MudSelectItem Value="@watermark">@watermark.Name</MudSelectItem>
}
</MudSelect>
</MudCardContent> </MudCardContent>
</MudCard> </MudCard>
</div> </div>
@ -299,6 +306,7 @@
private List<NamedMediaItemViewModel> _televisionSeasons; private List<NamedMediaItemViewModel> _televisionSeasons;
private List<NamedMediaItemViewModel> _artists; private List<NamedMediaItemViewModel> _artists;
private List<FillerPresetViewModel> _fillerPresets; private List<FillerPresetViewModel> _fillerPresets;
private List<WatermarkViewModel> _watermarks;
private ProgramScheduleItemEditViewModel _selectedItem; private ProgramScheduleItemEditViewModel _selectedItem;
@ -308,7 +316,10 @@
_cts.Dispose(); _cts.Dispose();
} }
protected override Task OnParametersSetAsync() => LoadScheduleItems(); protected override async Task OnParametersSetAsync()
{
await LoadScheduleItems();
}
private async Task LoadScheduleItems() private async Task LoadScheduleItems()
{ {
@ -327,6 +338,8 @@
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList()); .Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
_fillerPresets = await _mediator.Send(new GetAllFillerPresets(), _cts.Token) _fillerPresets = await _mediator.Send(new GetAllFillerPresets(), _cts.Token)
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList()); .Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
_watermarks = await _mediator.Send(new GetAllWatermarks(), _cts.Token)
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
string name = string.Empty; string name = string.Empty;
var shuffleScheduleItems = false; var shuffleScheduleItems = false;
@ -353,7 +366,7 @@
} }
} }
} }
private ProgramScheduleItemEditViewModel ProjectToEditViewModel(ProgramScheduleItemViewModel item) private ProgramScheduleItemEditViewModel ProjectToEditViewModel(ProgramScheduleItemViewModel item)
{ {
var result = new ProgramScheduleItemEditViewModel var result = new ProgramScheduleItemEditViewModel
@ -375,7 +388,8 @@
MidRollFiller = item.MidRollFiller, MidRollFiller = item.MidRollFiller,
PostRollFiller = item.PostRollFiller, PostRollFiller = item.PostRollFiller,
TailFiller = item.TailFiller, TailFiller = item.TailFiller,
FallbackFiller = item.FallbackFiller FallbackFiller = item.FallbackFiller,
Watermark = item.Watermark
}; };
switch (item) switch (item)
@ -427,9 +441,6 @@
(toSwap.Index, item.Index) = (item.Index, toSwap.Index); (toSwap.Index, item.Index) = (item.Index, toSwap.Index);
} }
private Task<IEnumerable<FillerPresetViewModel>> SearchFillerPresets(FillerKind fillerKind, string value) =>
_fillerPresets.Filter(p => p.FillerKind == fillerKind && p.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
private async Task SaveChanges() private async Task SaveChanges()
{ {
var items = _schedule.Items.Map(item => new ReplaceProgramScheduleItem( var items = _schedule.Items.Map(item => new ReplaceProgramScheduleItem(
@ -452,7 +463,8 @@
item.MidRollFiller?.Id, item.MidRollFiller?.Id,
item.PostRollFiller?.Id, item.PostRollFiller?.Id,
item.TailFiller?.Id, item.TailFiller?.Id,
item.FallbackFiller?.Id)).ToList(); item.FallbackFiller?.Id,
item.Watermark?.Id)).ToList();
Seq<BaseError> errorMessages = await _mediator.Send(new ReplaceProgramScheduleItems(Id, items), _cts.Token).Map(e => e.LeftToSeq()); Seq<BaseError> errorMessages = await _mediator.Send(new ReplaceProgramScheduleItems(Id, items), _cts.Token).Map(e => e.LeftToSeq());

2
ErsatzTV/Pages/TelevisionEpisodeList.razor

@ -219,7 +219,7 @@
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Cancelled && result.Data is ProgramScheduleViewModel schedule) if (!result.Cancelled && result.Data is ProgramScheduleViewModel schedule)
{ {
await _mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionSeason, null, null, null, SeasonId, PlaybackOrder.Shuffle, null, null, TailMode.None, null, GuideMode.Normal, null, null, null, null, null), _cts.Token); await _mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionSeason, null, null, null, SeasonId, PlaybackOrder.Shuffle, null, null, TailMode.None, null, GuideMode.Normal, null, null, null, null, null, null), _cts.Token);
_navigationManager.NavigateTo($"/schedules/{schedule.Id}/items"); _navigationManager.NavigateTo($"/schedules/{schedule.Id}/items");
} }
} }

2
ErsatzTV/Pages/TelevisionSeasonList.razor

@ -228,7 +228,7 @@
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Cancelled && result.Data is ProgramScheduleViewModel schedule) if (!result.Cancelled && result.Data is ProgramScheduleViewModel schedule)
{ {
await _mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionShow, null, null, null, ShowId, PlaybackOrder.Shuffle, null, null, TailMode.None, null, GuideMode.Normal, null, null, null, null, null), _cts.Token); await _mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionShow, null, null, null, ShowId, PlaybackOrder.Shuffle, null, null, TailMode.None, null, GuideMode.Normal, null, null, null, null, null, null), _cts.Token);
_navigationManager.NavigateTo($"/schedules/{schedule.Id}/items"); _navigationManager.NavigateTo($"/schedules/{schedule.Id}/items");
} }
} }

2
ErsatzTV/ViewModels/ProgramScheduleItemEditViewModel.cs

@ -4,6 +4,7 @@ using ErsatzTV.Annotations;
using ErsatzTV.Application.Filler; using ErsatzTV.Application.Filler;
using ErsatzTV.Application.MediaCollections; using ErsatzTV.Application.MediaCollections;
using ErsatzTV.Application.MediaItems; using ErsatzTV.Application.MediaItems;
using ErsatzTV.Application.Watermarks;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
namespace ErsatzTV.ViewModels; namespace ErsatzTV.ViewModels;
@ -63,6 +64,7 @@ public class ProgramScheduleItemEditViewModel : INotifyPropertyChanged
public FillerPresetViewModel PostRollFiller { get; set; } public FillerPresetViewModel PostRollFiller { get; set; }
public FillerPresetViewModel TailFiller { get; set; } public FillerPresetViewModel TailFiller { get; set; }
public FillerPresetViewModel FallbackFiller { get; set; } public FillerPresetViewModel FallbackFiller { get; set; }
public WatermarkViewModel Watermark { get; set; }
public string CollectionName => CollectionType switch public string CollectionName => CollectionType switch
{ {

Loading…
Cancel
Save