Browse Source

add option to allow watermarks over filler (#796)

pull/797/head
Jason Dove 3 years ago committed by GitHub
parent
commit
bd5b52922d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CHANGELOG.md
  2. 1
      ErsatzTV.Application/Filler/Commands/CreateFillerPreset.cs
  3. 1
      ErsatzTV.Application/Filler/Commands/CreateFillerPresetHandler.cs
  4. 1
      ErsatzTV.Application/Filler/Commands/UpdateFillerPreset.cs
  5. 4
      ErsatzTV.Application/Filler/Commands/UpdateFillerPresetHandler.cs
  6. 1
      ErsatzTV.Application/Filler/FillerPresetViewModel.cs
  7. 1
      ErsatzTV.Application/Filler/Mapper.cs
  8. 6
      ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs
  9. 3
      ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs
  10. 1
      ErsatzTV.Core/Domain/Filler/FillerPreset.cs
  11. 1
      ErsatzTV.Core/Domain/PlayoutItem.cs
  12. 8
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  13. 3
      ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs
  14. 64
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs
  15. 4285
      ErsatzTV.Infrastructure/Migrations/20220509214257_FillerPreset_AllowWatermarks.Designer.cs
  16. 37
      ErsatzTV.Infrastructure/Migrations/20220509214257_FillerPreset_AllowWatermarks.cs
  17. 6
      ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs
  18. 2
      ErsatzTV/Pages/FillerPresetEditor.razor
  19. 4
      ErsatzTV/ViewModels/FillerPresetEditViewModel.cs

2
CHANGELOG.md

@ -12,6 +12,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -12,6 +12,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Valid metadata kinds are `fallback`, `sidecar` (NFO), `external` (from a media server) and `embedded` (songs)
- Add autocomplete functionality to search bar to quickly navigate to channels, ffmpeg profiles, collections and schedules by name
- Add global setting to skip missing (file-not-found or unavailable) items when building playouts
- Add filler preset option to allow watermarks to overlay on top of filler (disabled by default)
- This option is applied when new items are added to a playout; rebuilding is needed if you want the change to take effect immediately
### Changed
- Replace invalid (control) characters in NFO metadata with replacement character `<EFBFBD>` before parsing

1
ErsatzTV.Application/Filler/Commands/CreateFillerPreset.cs

@ -11,6 +11,7 @@ public record CreateFillerPreset( @@ -11,6 +11,7 @@ public record CreateFillerPreset(
TimeSpan? Duration,
int? Count,
int? PadToNearestMinute,
bool AllowWatermarks,
ProgramScheduleItemCollectionType CollectionType,
int? CollectionId,
int? MediaItemId,

1
ErsatzTV.Application/Filler/Commands/CreateFillerPresetHandler.cs

@ -31,6 +31,7 @@ public class CreateFillerPresetHandler : IRequestHandler<CreateFillerPreset, Eit @@ -31,6 +31,7 @@ public class CreateFillerPresetHandler : IRequestHandler<CreateFillerPreset, Eit
Duration = request.Duration,
Count = request.Count,
PadToNearestMinute = request.PadToNearestMinute,
AllowWatermarks = request.AllowWatermarks,
CollectionType = request.CollectionType,
CollectionId = request.CollectionId,
MediaItemId = request.MediaItemId,

1
ErsatzTV.Application/Filler/Commands/UpdateFillerPreset.cs

@ -12,6 +12,7 @@ public record UpdateFillerPreset( @@ -12,6 +12,7 @@ public record UpdateFillerPreset(
TimeSpan? Duration,
int? Count,
int? PadToNearestMinute,
bool AllowWatermarks,
ProgramScheduleItemCollectionType CollectionType,
int? CollectionId,
int? MediaItemId,

4
ErsatzTV.Application/Filler/Commands/UpdateFillerPresetHandler.cs

@ -15,8 +15,7 @@ public class UpdateFillerPresetHandler : IRequestHandler<UpdateFillerPreset, Eit @@ -15,8 +15,7 @@ public class UpdateFillerPresetHandler : IRequestHandler<UpdateFillerPreset, Eit
public async Task<Either<BaseError, Unit>> Handle(UpdateFillerPreset request, CancellationToken cancellationToken)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, FillerPreset> validation = await FillerPresetMustExist(dbContext, request);
return await LanguageExtensions.Apply(validation, ps => ApplyUpdateRequest(dbContext, ps, request));
}
@ -32,6 +31,7 @@ public class UpdateFillerPresetHandler : IRequestHandler<UpdateFillerPreset, Eit @@ -32,6 +31,7 @@ public class UpdateFillerPresetHandler : IRequestHandler<UpdateFillerPreset, Eit
existing.Duration = request.Duration;
existing.Count = request.Count;
existing.PadToNearestMinute = request.PadToNearestMinute;
existing.AllowWatermarks = request.AllowWatermarks;
existing.CollectionType = request.CollectionType;
existing.CollectionId = request.CollectionId;
existing.MediaItemId = request.MediaItemId;

1
ErsatzTV.Application/Filler/FillerPresetViewModel.cs

@ -11,6 +11,7 @@ public record FillerPresetViewModel( @@ -11,6 +11,7 @@ public record FillerPresetViewModel(
TimeSpan? Duration,
int? Count,
int? PadToNearestMinute,
bool AllowWatermarks,
ProgramScheduleItemCollectionType CollectionType,
int? CollectionId,
int? MediaItemId,

1
ErsatzTV.Application/Filler/Mapper.cs

@ -13,6 +13,7 @@ internal static class Mapper @@ -13,6 +13,7 @@ internal static class Mapper
fillerPreset.Duration,
fillerPreset.Count,
fillerPreset.PadToNearestMinute,
fillerPreset.AllowWatermarks,
fillerPreset.CollectionType,
fillerPreset.CollectionId,
fillerPreset.MediaItemId,

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

@ -182,7 +182,8 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -182,7 +182,8 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
playoutItemWithPath.PlayoutItem.InPoint,
playoutItemWithPath.PlayoutItem.OutPoint,
request.PtsOffset,
request.TargetFramerate);
request.TargetFramerate,
playoutItemWithPath.PlayoutItem.DisableWatermarks);
var result = new PlayoutItemProcessModel(
process,
@ -371,7 +372,8 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -371,7 +372,8 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
Finish = finish.UtcDateTime,
FillerKind = FillerKind.Fallback,
InPoint = TimeSpan.Zero,
OutPoint = version.Duration
OutPoint = version.Duration,
DisableWatermarks = !fallbackPreset.AllowWatermarks
};
return await ValidatePlayoutItemPath(playoutItem);

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

@ -489,7 +489,8 @@ public class TranscodingTests @@ -489,7 +489,8 @@ public class TranscodingTests
TimeSpan.Zero,
TimeSpan.FromSeconds(5),
0,
None);
None,
false);
// Console.WriteLine($"ffmpeg arguments {string.Join(" ", process.StartInfo.ArgumentList)}");

1
ErsatzTV.Core/Domain/Filler/FillerPreset.cs

@ -9,6 +9,7 @@ public class FillerPreset @@ -9,6 +9,7 @@ public class FillerPreset
public TimeSpan? Duration { get; set; }
public int? Count { get; set; }
public int? PadToNearestMinute { get; set; }
public bool AllowWatermarks { get; set; }
public ProgramScheduleItemCollectionType CollectionType { get; set; }
public int? CollectionId { get; set; }
public Collection Collection { get; set; }

1
ErsatzTV.Core/Domain/PlayoutItem.cs

@ -22,6 +22,7 @@ public class PlayoutItem @@ -22,6 +22,7 @@ public class PlayoutItem
public string ChapterTitle { get; set; }
public ChannelWatermark Watermark { get; set; }
public int? WatermarkId { get; set; }
public bool DisableWatermarks { get; set; }
public string PreferredAudioLanguageCode { get; set; }
public string PreferredSubtitleLanguageCode { get; set; }
public ChannelSubtitleMode? SubtitleMode { get; set; }

8
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -59,7 +59,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -59,7 +59,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
TimeSpan inPoint,
TimeSpan outPoint,
long ptsOffset,
Option<int> targetFramerate)
Option<int> targetFramerate,
bool disableWatermarks)
{
MediaStream videoStream = await _ffmpegStreamSelector.SelectVideoStream(videoVersion);
Option<MediaStream> maybeAudioStream =
@ -90,8 +91,9 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -90,8 +91,9 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
hlsRealtime,
targetFramerate);
Option<WatermarkOptions> watermarkOptions =
await _ffmpegProcessService.GetWatermarkOptions(
Option<WatermarkOptions> watermarkOptions = disableWatermarks
? None
: await _ffmpegProcessService.GetWatermarkOptions(
ffprobePath,
channel,
playoutItemWatermark,

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

@ -33,7 +33,8 @@ public interface IFFmpegProcessService @@ -33,7 +33,8 @@ public interface IFFmpegProcessService
TimeSpan inPoint,
TimeSpan outPoint,
long ptsOffset,
Option<int> targetFramerate);
Option<int> targetFramerate,
bool disableWatermarks);
Task<Command> ForError(
string ffmpegPath,

64
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs

@ -93,7 +93,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -93,7 +93,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
InPoint = TimeSpan.Zero,
OutPoint = itemDuration,
FillerKind = FillerKind.Tail,
GuideGroup = nextState.NextGuideGroup
GuideGroup = nextState.NextGuideGroup,
DisableWatermarks = !scheduleItem.TailFiller.AllowWatermarks
};
newItems.Add(playoutItem);
@ -135,7 +136,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -135,7 +136,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
InPoint = TimeSpan.Zero,
OutPoint = TimeSpan.Zero,
GuideGroup = nextState.NextGuideGroup,
FillerKind = FillerKind.Fallback
FillerKind = FillerKind.Fallback,
DisableWatermarks = !scheduleItem.FallbackFiller.AllowWatermarks
};
newItems.Add(playoutItem);
@ -341,12 +343,22 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -341,12 +343,22 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
case FillerMode.Duration when filler.Duration.HasValue:
IMediaCollectionEnumerator e1 = enumerators[CollectionKey.ForFillerPreset(filler)];
result.AddRange(
AddDurationFiller(playoutBuilderState, e1, filler.Duration.Value, FillerKind.PreRoll));
AddDurationFiller(
playoutBuilderState,
e1,
filler.Duration.Value,
FillerKind.PreRoll,
filler.AllowWatermarks));
break;
case FillerMode.Count when filler.Count.HasValue:
IMediaCollectionEnumerator e2 = enumerators[CollectionKey.ForFillerPreset(filler)];
result.AddRange(
AddCountFiller(playoutBuilderState, e2, filler.Count.Value, FillerKind.PreRoll));
AddCountFiller(
playoutBuilderState,
e2,
filler.Count.Value,
FillerKind.PreRoll,
filler.AllowWatermarks));
break;
}
}
@ -374,7 +386,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -374,7 +386,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
playoutBuilderState,
e1,
filler.Duration.Value,
FillerKind.MidRoll));
FillerKind.MidRoll,
filler.AllowWatermarks));
}
}
@ -391,7 +404,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -391,7 +404,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
playoutBuilderState,
e2,
filler.Count.Value,
FillerKind.MidRoll));
FillerKind.MidRoll,
filler.AllowWatermarks));
}
}
@ -408,12 +422,22 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -408,12 +422,22 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
case FillerMode.Duration when filler.Duration.HasValue:
IMediaCollectionEnumerator e1 = enumerators[CollectionKey.ForFillerPreset(filler)];
result.AddRange(
AddDurationFiller(playoutBuilderState, e1, filler.Duration.Value, FillerKind.PostRoll));
AddDurationFiller(
playoutBuilderState,
e1,
filler.Duration.Value,
FillerKind.PostRoll,
filler.AllowWatermarks));
break;
case FillerMode.Count when filler.Count.HasValue:
IMediaCollectionEnumerator e2 = enumerators[CollectionKey.ForFillerPreset(filler)];
result.AddRange(
AddCountFiller(playoutBuilderState, e2, filler.Count.Value, FillerKind.PostRoll));
AddCountFiller(
playoutBuilderState,
e2,
filler.Count.Value,
FillerKind.PostRoll,
filler.AllowWatermarks));
break;
}
}
@ -464,7 +488,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -464,7 +488,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
playoutBuilderState,
pre1,
remainingToFill,
FillerKind.PreRoll));
FillerKind.PreRoll,
padFiller.AllowWatermarks));
totalDuration =
TimeSpan.FromMilliseconds(result.Sum(pi => (pi.Finish - pi.Start).TotalMilliseconds));
remainingToFill = targetTime - totalDuration - playoutItem.StartOffset;
@ -487,7 +512,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -487,7 +512,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
playoutBuilderState,
mid1,
remainingToFill,
FillerKind.MidRoll));
FillerKind.MidRoll,
padFiller.AllowWatermarks));
TimeSpan average = effectiveChapters.Count == 0
? remainingToFill
: remainingToFill / (effectiveChapters.Count - 1);
@ -540,7 +566,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -540,7 +566,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
playoutBuilderState,
post1,
remainingToFill,
FillerKind.PostRoll));
FillerKind.PostRoll,
padFiller.AllowWatermarks));
totalDuration =
TimeSpan.FromMilliseconds(result.Sum(pi => (pi.Finish - pi.Start).TotalMilliseconds));
remainingToFill = targetTime - totalDuration - playoutItem.StartOffset;
@ -576,7 +603,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -576,7 +603,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
PlayoutBuilderState playoutBuilderState,
IMediaCollectionEnumerator enumerator,
int count,
FillerKind fillerKind)
FillerKind fillerKind,
bool allowWatermarks)
{
var result = new List<PlayoutItem>();
@ -594,7 +622,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -594,7 +622,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
InPoint = TimeSpan.Zero,
OutPoint = itemDuration,
GuideGroup = playoutBuilderState.NextGuideGroup,
FillerKind = fillerKind
FillerKind = fillerKind,
DisableWatermarks = !allowWatermarks
};
result.Add(playoutItem);
@ -609,7 +638,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -609,7 +638,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
PlayoutBuilderState playoutBuilderState,
IMediaCollectionEnumerator enumerator,
TimeSpan duration,
FillerKind fillerKind)
FillerKind fillerKind,
bool allowWatermarks)
{
var result = new List<PlayoutItem>();
@ -632,7 +662,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -632,7 +662,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
InPoint = TimeSpan.Zero,
OutPoint = itemDuration,
GuideGroup = playoutBuilderState.NextGuideGroup,
FillerKind = fillerKind
FillerKind = fillerKind,
DisableWatermarks = !allowWatermarks
};
result.Add(playoutItem);
@ -670,7 +701,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -670,7 +701,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
InPoint = TimeSpan.Zero,
OutPoint = TimeSpan.Zero,
GuideGroup = playoutBuilderState.NextGuideGroup,
FillerKind = FillerKind.Fallback
FillerKind = FillerKind.Fallback,
DisableWatermarks = !scheduleItem.FallbackFiller.AllowWatermarks
};
enumerator.MoveNext();

4285
ErsatzTV.Infrastructure/Migrations/20220509214257_FillerPreset_AllowWatermarks.Designer.cs generated

File diff suppressed because it is too large Load Diff

37
ErsatzTV.Infrastructure/Migrations/20220509214257_FillerPreset_AllowWatermarks.cs

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class FillerPreset_AllowWatermarks : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "DisableWatermarks",
table: "PlayoutItem",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "AllowWatermarks",
table: "FillerPreset",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "DisableWatermarks",
table: "PlayoutItem");
migrationBuilder.DropColumn(
name: "AllowWatermarks",
table: "FillerPreset");
}
}
}

6
ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs

@ -589,6 +589,9 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -589,6 +589,9 @@ namespace ErsatzTV.Infrastructure.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("AllowWatermarks")
.HasColumnType("INTEGER");
b.Property<int?>("CollectionId")
.HasColumnType("INTEGER");
@ -1386,6 +1389,9 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -1386,6 +1389,9 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Property<string>("CustomTitle")
.HasColumnType("TEXT");
b.Property<bool>("DisableWatermarks")
.HasColumnType("INTEGER");
b.Property<int>("FillerKind")
.HasColumnType("INTEGER");

2
ErsatzTV/Pages/FillerPresetEditor.razor

@ -42,6 +42,7 @@ @@ -42,6 +42,7 @@
<MudSelectItem T="int?" Value="15">15 (:00, :15, :30, :45)</MudSelectItem>
<MudSelectItem T="int?" Value="30">30 (:00, :30)</MudSelectItem>
</MudSelect>
<MudCheckBox Class="mt-3" Label="Allow Watermarks" @bind-Checked="@_model.AllowWatermarks" For="@(() => _model.AllowWatermarks)"/>
<MudSelect Class="mt-3" Label="Filler Collection Type" @bind-Value="_model.CollectionType" For="@(() => _model.CollectionType)">
@foreach (ProgramScheduleItemCollectionType collectionType in Enum.GetValues<ProgramScheduleItemCollectionType>())
{
@ -181,6 +182,7 @@ @@ -181,6 +182,7 @@
_model.Duration = fillerPreset.Duration;
_model.Count = fillerPreset.Count;
_model.PadToNearestMinute = fillerPreset.PadToNearestMinute;
_model.AllowWatermarks = fillerPreset.AllowWatermarks;
_model.CollectionType = fillerPreset.CollectionType;
_model.Collection = fillerPreset.CollectionId.HasValue
? _mediaCollections.Find(c => c.Id == fillerPreset.CollectionId.Value)

4
ErsatzTV/ViewModels/FillerPresetEditViewModel.cs

@ -57,6 +57,8 @@ public class FillerPresetEditViewModel @@ -57,6 +57,8 @@ public class FillerPresetEditViewModel
set => _padToNearestMinute = value;
}
public bool AllowWatermarks { get; set; }
public ProgramScheduleItemCollectionType CollectionType
{
get => _collectionType;
@ -88,6 +90,7 @@ public class FillerPresetEditViewModel @@ -88,6 +90,7 @@ public class FillerPresetEditViewModel
Duration.Map(FixDuration),
Count,
PadToNearestMinute,
AllowWatermarks,
CollectionType,
Collection?.Id,
MediaItem?.MediaItemId,
@ -102,6 +105,7 @@ public class FillerPresetEditViewModel @@ -102,6 +105,7 @@ public class FillerPresetEditViewModel
Duration.Map(FixDuration),
Count,
PadToNearestMinute,
AllowWatermarks,
CollectionType,
Collection?.Id,
MediaItem?.MediaItemId,

Loading…
Cancel
Save