mirror of https://github.com/ErsatzTV/ErsatzTV.git
Browse Source
* first pass at default filler for block scheduling * configure default filler in ui * update changelogpull/1798/head
27 changed files with 24120 additions and 34 deletions
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Scheduling; |
||||
|
||||
namespace ErsatzTV.Core.Interfaces.Scheduling; |
||||
|
||||
public interface IBlockPlayoutFillerBuilder |
||||
{ |
||||
Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken); |
||||
} |
||||
@ -0,0 +1,170 @@
@@ -0,0 +1,170 @@
|
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Domain.Filler; |
||||
using ErsatzTV.Core.Domain.Scheduling; |
||||
using ErsatzTV.Core.Extensions; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using ErsatzTV.Core.Interfaces.Scheduling; |
||||
using Newtonsoft.Json; |
||||
|
||||
namespace ErsatzTV.Core.Scheduling.BlockScheduling; |
||||
|
||||
public class BlockPlayoutFillerBuilder( |
||||
IMediaCollectionRepository mediaCollectionRepository, |
||||
ITelevisionRepository televisionRepository, |
||||
IArtistRepository artistRepository) : IBlockPlayoutFillerBuilder |
||||
{ |
||||
private static readonly JsonSerializerSettings JsonSettings = new() |
||||
{ |
||||
NullValueHandling = NullValueHandling.Ignore |
||||
}; |
||||
|
||||
public async Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken) |
||||
{ |
||||
if (mode is PlayoutBuildMode.Reset) |
||||
{ |
||||
// remove all playout items with type filler
|
||||
var toRemove = playout.Items.Where(pi => pi.FillerKind is not FillerKind.None).ToList(); |
||||
foreach (PlayoutItem playoutItem in toRemove) |
||||
{ |
||||
BlockPlayoutChangeDetection.RemoveItemAndHistory(playout, playoutItem); |
||||
} |
||||
} |
||||
|
||||
var collectionEnumerators = new Dictionary<CollectionKey, IMediaCollectionEnumerator>(); |
||||
|
||||
// find all unscheduled periods
|
||||
var queue = new Queue<PlayoutItem>(playout.Items); |
||||
while (queue.Count > 1) |
||||
{ |
||||
PlayoutItem one = queue.Dequeue(); |
||||
PlayoutItem two = queue.Peek(); |
||||
|
||||
DateTimeOffset start = one.FinishOffset; |
||||
DateTimeOffset finish = two.StartOffset; |
||||
|
||||
// find applicable deco
|
||||
foreach (Deco deco in GetDecoFor(playout, start)) |
||||
{ |
||||
var collectionKey = CollectionKey.ForDecoDefaultFiller(deco); |
||||
string historyKey = HistoryDetails.ForDefaultFiller(deco); |
||||
|
||||
// load collection items from db on demand
|
||||
if (!collectionEnumerators.TryGetValue(collectionKey, out IMediaCollectionEnumerator enumerator)) |
||||
{ |
||||
List<MediaItem> collectionItems = await MediaItemsForCollection.Collect( |
||||
mediaCollectionRepository, |
||||
televisionRepository, |
||||
artistRepository, |
||||
collectionKey); |
||||
|
||||
enumerator = BlockPlayoutEnumerator.Shuffle( |
||||
collectionItems, |
||||
start, |
||||
playout, |
||||
deco, |
||||
historyKey); |
||||
|
||||
collectionEnumerators.Add(collectionKey, enumerator); |
||||
} |
||||
|
||||
DateTimeOffset current = start; |
||||
var pastTime = false; |
||||
while (current < finish) |
||||
{ |
||||
foreach (MediaItem mediaItem in enumerator.Current) |
||||
{ |
||||
TimeSpan itemDuration = DurationForMediaItem(mediaItem); |
||||
|
||||
// add filler from deco to unscheduled period
|
||||
var filler = new PlayoutItem |
||||
{ |
||||
MediaItemId = mediaItem.Id, |
||||
Start = current.UtcDateTime, |
||||
Finish = current.UtcDateTime + itemDuration, |
||||
InPoint = TimeSpan.Zero, |
||||
OutPoint = itemDuration, |
||||
FillerKind = FillerKind.Fallback, |
||||
CollectionKey = JsonConvert.SerializeObject(collectionKey, JsonSettings), |
||||
GuideStart = one.GuideStart, |
||||
GuideFinish = one.GuideFinish, |
||||
GuideGroup = one.GuideGroup |
||||
}; |
||||
|
||||
if (filler.FinishOffset > finish) |
||||
{ |
||||
pastTime = true; |
||||
break; |
||||
} |
||||
|
||||
playout.Items.Add(filler); |
||||
|
||||
|
||||
// create a playout history record
|
||||
var nextHistory = new PlayoutHistory |
||||
{ |
||||
PlayoutId = playout.Id, |
||||
PlaybackOrder = PlaybackOrder.Shuffle, |
||||
Index = enumerator.State.Index, |
||||
When = current.UtcDateTime, |
||||
Key = historyKey, |
||||
Details = HistoryDetails.ForMediaItem(mediaItem) |
||||
}; |
||||
|
||||
playout.PlayoutHistory.Add(nextHistory); |
||||
|
||||
current += itemDuration; |
||||
enumerator.MoveNext(); |
||||
} |
||||
|
||||
if (pastTime) |
||||
{ |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
return playout; |
||||
} |
||||
|
||||
private static Option<Deco> GetDecoFor(Playout playout, DateTimeOffset start) |
||||
{ |
||||
Option<PlayoutTemplate> maybeTemplate = PlayoutTemplateSelector.GetPlayoutTemplateFor(playout.Templates, start); |
||||
foreach (PlayoutTemplate template in maybeTemplate) |
||||
{ |
||||
if (template.DecoTemplate is not null) |
||||
{ |
||||
foreach (DecoTemplateItem decoTemplateItem in template.DecoTemplate.Items) |
||||
{ |
||||
if (decoTemplateItem.StartTime <= start.TimeOfDay && decoTemplateItem.EndTime > start.TimeOfDay) |
||||
{ |
||||
switch (decoTemplateItem.Deco.DefaultFillerMode) |
||||
{ |
||||
case DecoMode.Inherit: |
||||
return Optional(playout.Deco); |
||||
case DecoMode.Override: |
||||
return decoTemplateItem.Deco; |
||||
case DecoMode.Disable: |
||||
default: |
||||
return Option<Deco>.None; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return Optional(playout.Deco); |
||||
} |
||||
|
||||
private static TimeSpan DurationForMediaItem(MediaItem mediaItem) |
||||
{ |
||||
if (mediaItem is Image image) |
||||
{ |
||||
return TimeSpan.FromSeconds(image.ImageMetadata.Head().DurationSeconds ?? Image.DefaultSeconds); |
||||
} |
||||
|
||||
MediaVersion version = mediaItem.GetHeadVersion(); |
||||
return version.Duration; |
||||
} |
||||
} |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,160 @@
@@ -0,0 +1,160 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
||||
#nullable disable |
||||
|
||||
namespace ErsatzTV.Infrastructure.MySql.Migrations |
||||
{ |
||||
/// <inheritdoc />
|
||||
public partial class Add_Deco_DefaultFiller : Migration |
||||
{ |
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.AddColumn<int>( |
||||
name: "DefaultFillerCollectionId", |
||||
table: "Deco", |
||||
type: "int", |
||||
nullable: true); |
||||
|
||||
migrationBuilder.AddColumn<int>( |
||||
name: "DefaultFillerCollectionType", |
||||
table: "Deco", |
||||
type: "int", |
||||
nullable: false, |
||||
defaultValue: 0); |
||||
|
||||
migrationBuilder.AddColumn<int>( |
||||
name: "DefaultFillerMediaItemId", |
||||
table: "Deco", |
||||
type: "int", |
||||
nullable: true); |
||||
|
||||
migrationBuilder.AddColumn<int>( |
||||
name: "DefaultFillerMode", |
||||
table: "Deco", |
||||
type: "int", |
||||
nullable: false, |
||||
defaultValue: 0); |
||||
|
||||
migrationBuilder.AddColumn<int>( |
||||
name: "DefaultFillerMultiCollectionId", |
||||
table: "Deco", |
||||
type: "int", |
||||
nullable: true); |
||||
|
||||
migrationBuilder.AddColumn<int>( |
||||
name: "DefaultFillerSmartCollectionId", |
||||
table: "Deco", |
||||
type: "int", |
||||
nullable: true); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_Deco_DefaultFillerCollectionId", |
||||
table: "Deco", |
||||
column: "DefaultFillerCollectionId"); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_Deco_DefaultFillerMediaItemId", |
||||
table: "Deco", |
||||
column: "DefaultFillerMediaItemId"); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_Deco_DefaultFillerMultiCollectionId", |
||||
table: "Deco", |
||||
column: "DefaultFillerMultiCollectionId"); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_Deco_DefaultFillerSmartCollectionId", |
||||
table: "Deco", |
||||
column: "DefaultFillerSmartCollectionId"); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_Deco_Collection_DefaultFillerCollectionId", |
||||
table: "Deco", |
||||
column: "DefaultFillerCollectionId", |
||||
principalTable: "Collection", |
||||
principalColumn: "Id"); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_Deco_MediaItem_DefaultFillerMediaItemId", |
||||
table: "Deco", |
||||
column: "DefaultFillerMediaItemId", |
||||
principalTable: "MediaItem", |
||||
principalColumn: "Id"); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_Deco_MultiCollection_DefaultFillerMultiCollectionId", |
||||
table: "Deco", |
||||
column: "DefaultFillerMultiCollectionId", |
||||
principalTable: "MultiCollection", |
||||
principalColumn: "Id"); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_Deco_SmartCollection_DefaultFillerSmartCollectionId", |
||||
table: "Deco", |
||||
column: "DefaultFillerSmartCollectionId", |
||||
principalTable: "SmartCollection", |
||||
principalColumn: "Id"); |
||||
} |
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_Deco_Collection_DefaultFillerCollectionId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_Deco_MediaItem_DefaultFillerMediaItemId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_Deco_MultiCollection_DefaultFillerMultiCollectionId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_Deco_SmartCollection_DefaultFillerSmartCollectionId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropIndex( |
||||
name: "IX_Deco_DefaultFillerCollectionId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropIndex( |
||||
name: "IX_Deco_DefaultFillerMediaItemId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropIndex( |
||||
name: "IX_Deco_DefaultFillerMultiCollectionId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropIndex( |
||||
name: "IX_Deco_DefaultFillerSmartCollectionId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "DefaultFillerCollectionId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "DefaultFillerCollectionType", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "DefaultFillerMediaItemId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "DefaultFillerMode", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "DefaultFillerMultiCollectionId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "DefaultFillerSmartCollectionId", |
||||
table: "Deco"); |
||||
} |
||||
} |
||||
} |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
||||
#nullable disable |
||||
|
||||
namespace ErsatzTV.Infrastructure.MySql.Migrations |
||||
{ |
||||
/// <inheritdoc />
|
||||
public partial class Update_PlayoutHistory_BlockId_Optional : Migration |
||||
{ |
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.AlterColumn<int>( |
||||
name: "BlockId", |
||||
table: "PlayoutHistory", |
||||
type: "int", |
||||
nullable: true, |
||||
oldClrType: typeof(int), |
||||
oldType: "int"); |
||||
} |
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.AlterColumn<int>( |
||||
name: "BlockId", |
||||
table: "PlayoutHistory", |
||||
type: "int", |
||||
nullable: false, |
||||
defaultValue: 0, |
||||
oldClrType: typeof(int), |
||||
oldType: "int", |
||||
oldNullable: true); |
||||
} |
||||
} |
||||
} |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,160 @@
@@ -0,0 +1,160 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
||||
#nullable disable |
||||
|
||||
namespace ErsatzTV.Infrastructure.Sqlite.Migrations |
||||
{ |
||||
/// <inheritdoc />
|
||||
public partial class Add_Deco_DefaultFiller : Migration |
||||
{ |
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.AddColumn<int>( |
||||
name: "DefaultFillerCollectionId", |
||||
table: "Deco", |
||||
type: "INTEGER", |
||||
nullable: true); |
||||
|
||||
migrationBuilder.AddColumn<int>( |
||||
name: "DefaultFillerCollectionType", |
||||
table: "Deco", |
||||
type: "INTEGER", |
||||
nullable: false, |
||||
defaultValue: 0); |
||||
|
||||
migrationBuilder.AddColumn<int>( |
||||
name: "DefaultFillerMediaItemId", |
||||
table: "Deco", |
||||
type: "INTEGER", |
||||
nullable: true); |
||||
|
||||
migrationBuilder.AddColumn<int>( |
||||
name: "DefaultFillerMode", |
||||
table: "Deco", |
||||
type: "INTEGER", |
||||
nullable: false, |
||||
defaultValue: 0); |
||||
|
||||
migrationBuilder.AddColumn<int>( |
||||
name: "DefaultFillerMultiCollectionId", |
||||
table: "Deco", |
||||
type: "INTEGER", |
||||
nullable: true); |
||||
|
||||
migrationBuilder.AddColumn<int>( |
||||
name: "DefaultFillerSmartCollectionId", |
||||
table: "Deco", |
||||
type: "INTEGER", |
||||
nullable: true); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_Deco_DefaultFillerCollectionId", |
||||
table: "Deco", |
||||
column: "DefaultFillerCollectionId"); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_Deco_DefaultFillerMediaItemId", |
||||
table: "Deco", |
||||
column: "DefaultFillerMediaItemId"); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_Deco_DefaultFillerMultiCollectionId", |
||||
table: "Deco", |
||||
column: "DefaultFillerMultiCollectionId"); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_Deco_DefaultFillerSmartCollectionId", |
||||
table: "Deco", |
||||
column: "DefaultFillerSmartCollectionId"); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_Deco_Collection_DefaultFillerCollectionId", |
||||
table: "Deco", |
||||
column: "DefaultFillerCollectionId", |
||||
principalTable: "Collection", |
||||
principalColumn: "Id"); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_Deco_MediaItem_DefaultFillerMediaItemId", |
||||
table: "Deco", |
||||
column: "DefaultFillerMediaItemId", |
||||
principalTable: "MediaItem", |
||||
principalColumn: "Id"); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_Deco_MultiCollection_DefaultFillerMultiCollectionId", |
||||
table: "Deco", |
||||
column: "DefaultFillerMultiCollectionId", |
||||
principalTable: "MultiCollection", |
||||
principalColumn: "Id"); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_Deco_SmartCollection_DefaultFillerSmartCollectionId", |
||||
table: "Deco", |
||||
column: "DefaultFillerSmartCollectionId", |
||||
principalTable: "SmartCollection", |
||||
principalColumn: "Id"); |
||||
} |
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_Deco_Collection_DefaultFillerCollectionId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_Deco_MediaItem_DefaultFillerMediaItemId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_Deco_MultiCollection_DefaultFillerMultiCollectionId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_Deco_SmartCollection_DefaultFillerSmartCollectionId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropIndex( |
||||
name: "IX_Deco_DefaultFillerCollectionId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropIndex( |
||||
name: "IX_Deco_DefaultFillerMediaItemId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropIndex( |
||||
name: "IX_Deco_DefaultFillerMultiCollectionId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropIndex( |
||||
name: "IX_Deco_DefaultFillerSmartCollectionId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "DefaultFillerCollectionId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "DefaultFillerCollectionType", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "DefaultFillerMediaItemId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "DefaultFillerMode", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "DefaultFillerMultiCollectionId", |
||||
table: "Deco"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "DefaultFillerSmartCollectionId", |
||||
table: "Deco"); |
||||
} |
||||
} |
||||
} |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
||||
#nullable disable |
||||
|
||||
namespace ErsatzTV.Infrastructure.Sqlite.Migrations |
||||
{ |
||||
/// <inheritdoc />
|
||||
public partial class Update_PlayoutHistory_BlockId_Optional : Migration |
||||
{ |
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.AlterColumn<int>( |
||||
name: "BlockId", |
||||
table: "PlayoutHistory", |
||||
type: "INTEGER", |
||||
nullable: true, |
||||
oldClrType: typeof(int), |
||||
oldType: "INTEGER"); |
||||
} |
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.AlterColumn<int>( |
||||
name: "BlockId", |
||||
table: "PlayoutHistory", |
||||
type: "INTEGER", |
||||
nullable: false, |
||||
defaultValue: 0, |
||||
oldClrType: typeof(int), |
||||
oldType: "INTEGER", |
||||
oldNullable: true); |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue