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/). @@ -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)
- 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
- Add `Watermark` setting to schedule items; this allows override the channel watermark. Watermark priority is:
- Schedule Item
- Channel
- Global
### Changed
- 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( @@ -23,4 +23,5 @@ public record AddProgramScheduleItem(
int? MidRollFillerId,
int? PostRollFillerId,
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 @@ -22,4 +22,5 @@ public interface IProgramScheduleItemRequest
int? PostRollFillerId { get; }
int? TailFillerId { get; }
int? FallbackFillerId { get; }
int? WatermarkId { get; }
}

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

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

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

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

12
ErsatzTV.Application/ProgramSchedules/Mapper.cs

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

7
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemDurationViewModel.cs

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

7
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemFloodViewModel.cs

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

7
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs

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

7
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemOneViewModel.cs

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

4
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemViewModel.cs

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

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

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

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

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

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

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

2
ErsatzTV.Core/Domain/PlayoutItem.cs

@ -20,6 +20,8 @@ public class PlayoutItem @@ -20,6 +20,8 @@ public class PlayoutItem
public TimeSpan InPoint { get; set; }
public TimeSpan OutPoint { 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 FinishOffset => new DateTimeOffset(Finish, TimeSpan.Zero).ToLocalTime();

2
ErsatzTV.Core/Domain/ProgramScheduleItem.cs

@ -32,4 +32,6 @@ public abstract class ProgramScheduleItem @@ -32,4 +32,6 @@ 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; }
}

4
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

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

35
ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs

@ -164,6 +164,7 @@ public class FFmpegProcessService @@ -164,6 +164,7 @@ public class FFmpegProcessService
string ffprobePath,
Option<string> subtitleFile,
Channel channel,
Option<ChannelWatermark> playoutItemWatermark,
Option<ChannelWatermark> globalWatermark,
MediaVersion videoVersion,
string videoPath,
@ -199,6 +200,7 @@ public class FFmpegProcessService @@ -199,6 +200,7 @@ public class FFmpegProcessService
await GetWatermarkOptions(
ffprobePath,
channel,
playoutItemWatermark,
globalWatermark,
videoVersion,
watermarkOverride,
@ -273,6 +275,7 @@ public class FFmpegProcessService @@ -273,6 +275,7 @@ public class FFmpegProcessService
internal async Task<WatermarkOptions> GetWatermarkOptions(
string ffprobePath,
Channel channel,
Option<ChannelWatermark> playoutItemWatermark,
Option<ChannelWatermark> globalWatermark,
MediaVersion videoVersion,
Option<ChannelWatermark> watermarkOverride,
@ -288,6 +291,38 @@ public class FFmpegProcessService @@ -288,6 +291,38 @@ public class FFmpegProcessService
0,
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
if (channel.Watermark != null)

2
ErsatzTV.Core/FFmpeg/SongVideoGenerator.cs

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

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

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

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

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

3
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs

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

3
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs

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

3
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs

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

3
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerOne.cs

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

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

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

2
ErsatzTV/Pages/Artist.razor

@ -238,7 +238,7 @@ @@ -238,7 +238,7 @@
DialogResult result = await dialog.Result;
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");
}
}

26
ErsatzTV/Pages/ScheduleItemsEditor.razor

@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
@using ErsatzTV.Application.Filler
@using ErsatzTV.Core.Domain.Filler
@using ErsatzTV.Application.Artists
@using ErsatzTV.Application.Watermarks
@implements IDisposable
@inject NavigationManager _navigationManager
@inject ILogger<ScheduleItemsEditor> _logger
@ -221,6 +222,12 @@ @@ -221,6 +222,12 @@
<MudSelectItem Value="@GuideMode.Normal">Normal</MudSelectItem>
<MudSelectItem Value="@GuideMode.Filler">Filler</MudSelectItem>
</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>
</MudCard>
</div>
@ -299,6 +306,7 @@ @@ -299,6 +306,7 @@
private List<NamedMediaItemViewModel> _televisionSeasons;
private List<NamedMediaItemViewModel> _artists;
private List<FillerPresetViewModel> _fillerPresets;
private List<WatermarkViewModel> _watermarks;
private ProgramScheduleItemEditViewModel _selectedItem;
@ -308,7 +316,10 @@ @@ -308,7 +316,10 @@
_cts.Dispose();
}
protected override Task OnParametersSetAsync() => LoadScheduleItems();
protected override async Task OnParametersSetAsync()
{
await LoadScheduleItems();
}
private async Task LoadScheduleItems()
{
@ -327,6 +338,8 @@ @@ -327,6 +338,8 @@
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
_fillerPresets = await _mediator.Send(new GetAllFillerPresets(), _cts.Token)
.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;
var shuffleScheduleItems = false;
@ -353,7 +366,7 @@ @@ -353,7 +366,7 @@
}
}
}
private ProgramScheduleItemEditViewModel ProjectToEditViewModel(ProgramScheduleItemViewModel item)
{
var result = new ProgramScheduleItemEditViewModel
@ -375,7 +388,8 @@ @@ -375,7 +388,8 @@
MidRollFiller = item.MidRollFiller,
PostRollFiller = item.PostRollFiller,
TailFiller = item.TailFiller,
FallbackFiller = item.FallbackFiller
FallbackFiller = item.FallbackFiller,
Watermark = item.Watermark
};
switch (item)
@ -427,9 +441,6 @@ @@ -427,9 +441,6 @@
(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()
{
var items = _schedule.Items.Map(item => new ReplaceProgramScheduleItem(
@ -452,7 +463,8 @@ @@ -452,7 +463,8 @@
item.MidRollFiller?.Id,
item.PostRollFiller?.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());

2
ErsatzTV/Pages/TelevisionEpisodeList.razor

@ -219,7 +219,7 @@ @@ -219,7 +219,7 @@
DialogResult result = await dialog.Result;
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");
}
}

2
ErsatzTV/Pages/TelevisionSeasonList.razor

@ -228,7 +228,7 @@ @@ -228,7 +228,7 @@
DialogResult result = await dialog.Result;
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");
}
}

2
ErsatzTV/ViewModels/ProgramScheduleItemEditViewModel.cs

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

Loading…
Cancel
Save