evenly divide epg time for schedule blocks (#1607)
* add checkbox to include block items in program guide * evenly divide epg time for schedule blocks
This commit is contained in:
@@ -159,27 +159,81 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
|
||||
.ThenInclude(sm => sm.Studios)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
List<PlayoutItem> sorted = [];
|
||||
|
||||
foreach (Playout playout in playouts)
|
||||
{
|
||||
switch (playout.ProgramSchedulePlayoutType)
|
||||
{
|
||||
case ProgramSchedulePlayoutType.Flood:
|
||||
case ProgramSchedulePlayoutType.Block:
|
||||
sorted.AddRange(playouts.Collect(p => p.Items).OrderBy(pi => pi.Start));
|
||||
break;
|
||||
case ProgramSchedulePlayoutType.ExternalJson:
|
||||
sorted.AddRange(await CollectExternalJsonItems(playout.ExternalJsonFile));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await using RecyclableMemoryStream ms = _recyclableMemoryStreamManager.GetStream();
|
||||
await using var xml = XmlWriter.Create(
|
||||
ms,
|
||||
new XmlWriterSettings { Async = true, ConformanceLevel = ConformanceLevel.Fragment });
|
||||
|
||||
foreach (Playout playout in playouts)
|
||||
{
|
||||
switch (playout.ProgramSchedulePlayoutType)
|
||||
{
|
||||
case ProgramSchedulePlayoutType.Flood:
|
||||
var floodSorted = playouts.Collect(p => p.Items).OrderBy(pi => pi.Start).ToList();
|
||||
await WritePlayoutXml(
|
||||
request,
|
||||
floodSorted,
|
||||
templateContext,
|
||||
movieTemplate,
|
||||
episodeTemplate,
|
||||
musicVideoTemplate,
|
||||
songTemplate,
|
||||
otherVideoTemplate,
|
||||
minifier,
|
||||
xml);
|
||||
break;
|
||||
case ProgramSchedulePlayoutType.Block:
|
||||
var blockSorted = playouts.Collect(p => p.Items).OrderBy(pi => pi.Start).ToList();
|
||||
await WriteBlockPlayoutXml(
|
||||
request,
|
||||
blockSorted,
|
||||
templateContext,
|
||||
movieTemplate,
|
||||
episodeTemplate,
|
||||
musicVideoTemplate,
|
||||
songTemplate,
|
||||
otherVideoTemplate,
|
||||
minifier,
|
||||
xml);
|
||||
break;
|
||||
case ProgramSchedulePlayoutType.ExternalJson:
|
||||
List<PlayoutItem> externalJsonSorted = await CollectExternalJsonItems(playout.ExternalJsonFile);
|
||||
await WritePlayoutXml(
|
||||
request,
|
||||
externalJsonSorted,
|
||||
templateContext,
|
||||
movieTemplate,
|
||||
episodeTemplate,
|
||||
musicVideoTemplate,
|
||||
songTemplate,
|
||||
otherVideoTemplate,
|
||||
minifier,
|
||||
xml);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await xml.FlushAsync();
|
||||
|
||||
string tempFile = Path.GetTempFileName();
|
||||
await File.WriteAllBytesAsync(tempFile, ms.ToArray(), cancellationToken);
|
||||
|
||||
string targetFile = Path.Combine(FileSystemLayout.ChannelGuideCacheFolder, $"{request.ChannelNumber}.xml");
|
||||
File.Move(tempFile, targetFile, true);
|
||||
}
|
||||
|
||||
private async Task WritePlayoutXml(
|
||||
RefreshChannelData request,
|
||||
List<PlayoutItem> sorted,
|
||||
XmlTemplateContext templateContext,
|
||||
Template movieTemplate,
|
||||
Template episodeTemplate,
|
||||
Template musicVideoTemplate,
|
||||
Template songTemplate,
|
||||
Template otherVideoTemplate,
|
||||
XmlMinifier minifier,
|
||||
XmlWriter xml)
|
||||
{
|
||||
// skip all filler that isn't pre-roll
|
||||
var i = 0;
|
||||
while (i < sorted.Count && sorted[i].FillerKind != FillerKind.None &&
|
||||
@@ -232,83 +286,159 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
|
||||
: finishItem.FinishOffset.ToString("yyyyMMddHHmmss zzz", CultureInfo.InvariantCulture)
|
||||
.Replace(":", string.Empty);
|
||||
|
||||
string title = GetTitle(displayItem);
|
||||
string subtitle = GetSubtitle(displayItem);
|
||||
|
||||
Option<string> maybeTemplateOutput = displayItem.MediaItem switch
|
||||
{
|
||||
Movie templateMovie => await ProcessMovieTemplate(
|
||||
request,
|
||||
templateMovie,
|
||||
start,
|
||||
stop,
|
||||
hasCustomTitle,
|
||||
displayItem,
|
||||
title,
|
||||
templateContext,
|
||||
movieTemplate),
|
||||
Episode templateEpisode => await ProcessEpisodeTemplate(
|
||||
request,
|
||||
templateEpisode,
|
||||
start,
|
||||
stop,
|
||||
hasCustomTitle,
|
||||
displayItem,
|
||||
title,
|
||||
subtitle,
|
||||
templateContext,
|
||||
episodeTemplate),
|
||||
MusicVideo templateMusicVideo => await ProcessMusicVideoTemplate(
|
||||
request,
|
||||
templateMusicVideo,
|
||||
start,
|
||||
stop,
|
||||
hasCustomTitle,
|
||||
displayItem,
|
||||
title,
|
||||
subtitle,
|
||||
templateContext,
|
||||
musicVideoTemplate),
|
||||
Song templateSong => await ProcessSongTemplate(
|
||||
request,
|
||||
templateSong,
|
||||
start,
|
||||
stop,
|
||||
hasCustomTitle,
|
||||
displayItem,
|
||||
title,
|
||||
subtitle,
|
||||
templateContext,
|
||||
songTemplate),
|
||||
OtherVideo templateOtherVideo => await ProcessOtherVideoTemplate(
|
||||
request,
|
||||
templateOtherVideo,
|
||||
start,
|
||||
stop,
|
||||
hasCustomTitle,
|
||||
displayItem,
|
||||
title,
|
||||
templateContext,
|
||||
otherVideoTemplate),
|
||||
_ => Option<string>.None
|
||||
};
|
||||
|
||||
foreach (string templateOutput in maybeTemplateOutput)
|
||||
{
|
||||
MarkupMinificationResult minified = minifier.Minify(templateOutput);
|
||||
await xml.WriteRawAsync(minified.MinifiedContent);
|
||||
}
|
||||
await WriteItemToXml(
|
||||
request,
|
||||
displayItem,
|
||||
start,
|
||||
stop,
|
||||
hasCustomTitle,
|
||||
templateContext,
|
||||
movieTemplate,
|
||||
episodeTemplate,
|
||||
musicVideoTemplate,
|
||||
songTemplate,
|
||||
otherVideoTemplate,
|
||||
minifier,
|
||||
xml);
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WriteBlockPlayoutXml(
|
||||
RefreshChannelData request,
|
||||
List<PlayoutItem> sorted,
|
||||
XmlTemplateContext templateContext,
|
||||
Template movieTemplate,
|
||||
Template episodeTemplate,
|
||||
Template musicVideoTemplate,
|
||||
Template songTemplate,
|
||||
Template otherVideoTemplate,
|
||||
XmlMinifier minifier,
|
||||
XmlWriter xml)
|
||||
{
|
||||
var groups = sorted.GroupBy(s => new { s.GuideStart, s.GuideFinish, s.GuideGroup });
|
||||
foreach (var group in groups)
|
||||
{
|
||||
DateTime groupStart = group.Key.GuideStart!.Value;
|
||||
DateTime groupFinish = group.Key.GuideFinish!.Value;
|
||||
TimeSpan groupDuration = groupFinish - groupStart;
|
||||
|
||||
await xml.FlushAsync();
|
||||
var itemsToInclude = group.Filter(g => g.FillerKind is FillerKind.None).ToList();
|
||||
TimeSpan perItem = groupDuration / itemsToInclude.Count;
|
||||
|
||||
string tempFile = Path.GetTempFileName();
|
||||
await File.WriteAllBytesAsync(tempFile, ms.ToArray(), cancellationToken);
|
||||
DateTimeOffset currentStart = new DateTimeOffset(groupStart, TimeSpan.Zero).ToLocalTime();
|
||||
DateTimeOffset currentFinish = currentStart + perItem;
|
||||
|
||||
string targetFile = Path.Combine(FileSystemLayout.ChannelGuideCacheFolder, $"{request.ChannelNumber}.xml");
|
||||
File.Move(tempFile, targetFile, true);
|
||||
foreach (PlayoutItem item in itemsToInclude)
|
||||
{
|
||||
string start = currentStart.ToString("yyyyMMddHHmmss zzz", CultureInfo.InvariantCulture)
|
||||
.Replace(":", string.Empty);
|
||||
string stop = currentFinish.ToString("yyyyMMddHHmmss zzz", CultureInfo.InvariantCulture)
|
||||
.Replace(":", string.Empty);
|
||||
|
||||
await WriteItemToXml(
|
||||
request,
|
||||
item,
|
||||
start,
|
||||
stop,
|
||||
hasCustomTitle: false,
|
||||
templateContext,
|
||||
movieTemplate,
|
||||
episodeTemplate,
|
||||
musicVideoTemplate,
|
||||
songTemplate,
|
||||
otherVideoTemplate,
|
||||
minifier,
|
||||
xml);
|
||||
|
||||
currentStart = currentFinish;
|
||||
currentFinish += perItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WriteItemToXml(
|
||||
RefreshChannelData request,
|
||||
PlayoutItem displayItem,
|
||||
string start,
|
||||
string stop,
|
||||
bool hasCustomTitle,
|
||||
XmlTemplateContext templateContext,
|
||||
Template movieTemplate,
|
||||
Template episodeTemplate,
|
||||
Template musicVideoTemplate,
|
||||
Template songTemplate,
|
||||
Template otherVideoTemplate,
|
||||
XmlMinifier minifier,
|
||||
XmlWriter xml)
|
||||
{
|
||||
string title = GetTitle(displayItem);
|
||||
string subtitle = GetSubtitle(displayItem);
|
||||
|
||||
Option<string> maybeTemplateOutput = displayItem.MediaItem switch
|
||||
{
|
||||
Movie templateMovie => await ProcessMovieTemplate(
|
||||
request,
|
||||
templateMovie,
|
||||
start,
|
||||
stop,
|
||||
hasCustomTitle,
|
||||
displayItem,
|
||||
title,
|
||||
templateContext,
|
||||
movieTemplate),
|
||||
Episode templateEpisode => await ProcessEpisodeTemplate(
|
||||
request,
|
||||
templateEpisode,
|
||||
start,
|
||||
stop,
|
||||
hasCustomTitle,
|
||||
displayItem,
|
||||
title,
|
||||
subtitle,
|
||||
templateContext,
|
||||
episodeTemplate),
|
||||
MusicVideo templateMusicVideo => await ProcessMusicVideoTemplate(
|
||||
request,
|
||||
templateMusicVideo,
|
||||
start,
|
||||
stop,
|
||||
hasCustomTitle,
|
||||
displayItem,
|
||||
title,
|
||||
subtitle,
|
||||
templateContext,
|
||||
musicVideoTemplate),
|
||||
Song templateSong => await ProcessSongTemplate(
|
||||
request,
|
||||
templateSong,
|
||||
start,
|
||||
stop,
|
||||
hasCustomTitle,
|
||||
displayItem,
|
||||
title,
|
||||
subtitle,
|
||||
templateContext,
|
||||
songTemplate),
|
||||
OtherVideo templateOtherVideo => await ProcessOtherVideoTemplate(
|
||||
request,
|
||||
templateOtherVideo,
|
||||
start,
|
||||
stop,
|
||||
hasCustomTitle,
|
||||
displayItem,
|
||||
title,
|
||||
templateContext,
|
||||
otherVideoTemplate),
|
||||
_ => Option<string>.None
|
||||
};
|
||||
|
||||
foreach (string templateOutput in maybeTemplateOutput)
|
||||
{
|
||||
MarkupMinificationResult minified = minifier.Minify(templateOutput);
|
||||
await xml.WriteRawAsync(minified.MinifiedContent);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<Option<string>> ProcessMovieTemplate(
|
||||
|
||||
@@ -12,4 +12,5 @@ public record BlockItemViewModel(
|
||||
MultiCollectionViewModel MultiCollection,
|
||||
SmartCollectionViewModel SmartCollection,
|
||||
NamedMediaItemViewModel MediaItem,
|
||||
PlaybackOrder PlaybackOrder);
|
||||
PlaybackOrder PlaybackOrder,
|
||||
bool IncludeInProgramGuide);
|
||||
|
||||
@@ -9,4 +9,5 @@ public record ReplaceBlockItem(
|
||||
int? MultiCollectionId,
|
||||
int? SmartCollectionId,
|
||||
int? MediaItemId,
|
||||
PlaybackOrder PlaybackOrder);
|
||||
PlaybackOrder PlaybackOrder,
|
||||
bool IncludeInProgramGuide);
|
||||
|
||||
@@ -53,7 +53,8 @@ public class ReplaceBlockItemsHandler(IDbContextFactory<TvContext> dbContextFact
|
||||
MultiCollectionId = item.MultiCollectionId,
|
||||
SmartCollectionId = item.SmartCollectionId,
|
||||
MediaItemId = item.MediaItemId,
|
||||
PlaybackOrder = item.PlaybackOrder
|
||||
PlaybackOrder = item.PlaybackOrder,
|
||||
IncludeInProgramGuide = item.IncludeInProgramGuide
|
||||
};
|
||||
|
||||
private static Task<Validation<BaseError, Block>> Validate(TvContext dbContext, ReplaceBlockItems request) =>
|
||||
|
||||
@@ -30,7 +30,8 @@ internal static class Mapper
|
||||
Artist artist => MediaItems.Mapper.ProjectToViewModel(artist),
|
||||
_ => null
|
||||
},
|
||||
blockItem.PlaybackOrder);
|
||||
blockItem.PlaybackOrder,
|
||||
blockItem.IncludeInProgramGuide);
|
||||
|
||||
internal static TemplateGroupViewModel ProjectToViewModel(TemplateGroup templateGroup) =>
|
||||
new(templateGroup.Id, templateGroup.Name, templateGroup.Templates.Count);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Domain.Scheduling;
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@@ -11,7 +12,7 @@ public class GetBlockItemsHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||
{
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
return await dbContext.BlockItems
|
||||
List<BlockItem> allItems = await dbContext.BlockItems
|
||||
.AsNoTracking()
|
||||
.Filter(i => i.BlockId == request.BlockId)
|
||||
.Include(i => i.Collection)
|
||||
@@ -30,7 +31,16 @@ public class GetBlockItemsHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||
.Include(i => i.MediaItem)
|
||||
.ThenInclude(i => (i as Artist).ArtistMetadata)
|
||||
.ThenInclude(am => am.Artwork)
|
||||
.ToListAsync(cancellationToken)
|
||||
.Map(items => items.Map(Mapper.ProjectToViewModel).ToList());
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
if (allItems.All(bi => bi.IncludeInProgramGuide == false))
|
||||
{
|
||||
foreach (BlockItem bi in allItems)
|
||||
{
|
||||
bi.IncludeInProgramGuide = true;
|
||||
}
|
||||
}
|
||||
|
||||
return allItems.Map(Mapper.ProjectToViewModel).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +64,8 @@ public static class BlockPlayoutChangeDetectionTests
|
||||
|
||||
List<EffectiveBlock> effectiveBlocks =
|
||||
[
|
||||
new EffectiveBlock(block1, blockKey1, GetLocalDate(2024, 1, 17).AddHours(9)),
|
||||
new EffectiveBlock(block2, blockKey2, GetLocalDate(2024, 1, 17).AddHours(13)),
|
||||
new EffectiveBlock(block1, blockKey1, GetLocalDate(2024, 1, 17).AddHours(9), 1),
|
||||
new EffectiveBlock(block2, blockKey2, GetLocalDate(2024, 1, 17).AddHours(13), 2),
|
||||
];
|
||||
|
||||
Map<CollectionKey, string> collectionEtags = LanguageExt.Map<CollectionKey, string>.Empty;
|
||||
|
||||
@@ -11,6 +11,7 @@ public class PlayoutItem
|
||||
public MediaItem MediaItem { get; set; }
|
||||
public DateTime Start { get; set; }
|
||||
public DateTime Finish { get; set; }
|
||||
public DateTime? GuideStart { get; set; }
|
||||
public DateTime? GuideFinish { get; set; }
|
||||
public string CustomTitle { get; set; }
|
||||
public int GuideGroup { get; set; }
|
||||
|
||||
@@ -16,4 +16,5 @@ public class BlockItem
|
||||
public int? SmartCollectionId { get; set; }
|
||||
public SmartCollection SmartCollection { get; set; }
|
||||
public PlaybackOrder PlaybackOrder { get; set; }
|
||||
public bool IncludeInProgramGuide { get; set; }
|
||||
}
|
||||
|
||||
@@ -32,8 +32,6 @@ public class BlockPlayoutBuilder(
|
||||
playout.Channel.Number,
|
||||
playout.Channel.Name);
|
||||
|
||||
var random = new Random();
|
||||
|
||||
List<PlaybackOrder> allowedPlaybackOrders =
|
||||
[
|
||||
PlaybackOrder.Chronological,
|
||||
@@ -149,14 +147,16 @@ public class BlockPlayoutBuilder(
|
||||
Finish = currentTime.UtcDateTime + itemDuration,
|
||||
InPoint = TimeSpan.Zero,
|
||||
OutPoint = itemDuration,
|
||||
FillerKind = FillerKind.None,
|
||||
FillerKind = blockItem.IncludeInProgramGuide ? FillerKind.None : FillerKind.GuideMode,
|
||||
//CustomTitle = scheduleItem.CustomTitle,
|
||||
//WatermarkId = scheduleItem.WatermarkId,
|
||||
//PreferredAudioLanguageCode = scheduleItem.PreferredAudioLanguageCode,
|
||||
//PreferredAudioTitle = scheduleItem.PreferredAudioTitle,
|
||||
//PreferredSubtitleLanguageCode = scheduleItem.PreferredSubtitleLanguageCode,
|
||||
//SubtitleMode = scheduleItem.SubtitleMode
|
||||
GuideGroup = random.Next(),
|
||||
GuideGroup = effectiveBlock.TemplateItemId,
|
||||
GuideStart = effectiveBlock.Start.UtcDateTime,
|
||||
GuideFinish = blockFinish.UtcDateTime,
|
||||
BlockKey = JsonConvert.SerializeObject(effectiveBlock.BlockKey),
|
||||
CollectionKey = JsonConvert.SerializeObject(collectionKey, JsonSettings),
|
||||
CollectionEtag = collectionEtags[collectionKey]
|
||||
|
||||
@@ -2,7 +2,7 @@ using ErsatzTV.Core.Domain.Scheduling;
|
||||
|
||||
namespace ErsatzTV.Core.Scheduling.BlockScheduling;
|
||||
|
||||
internal record EffectiveBlock(Block Block, BlockKey BlockKey, DateTimeOffset Start)
|
||||
internal record EffectiveBlock(Block Block, BlockKey BlockKey, DateTimeOffset Start, int TemplateItemId)
|
||||
{
|
||||
public static List<EffectiveBlock> GetEffectiveBlocks(
|
||||
ICollection<PlayoutTemplate> templates,
|
||||
@@ -27,6 +27,7 @@ internal record EffectiveBlock(Block Block, BlockKey BlockKey, DateTimeOffset St
|
||||
|
||||
var newBlocks = playoutTemplate.Template.Items
|
||||
.Map(i => ToEffectiveBlock(playoutTemplate, i, today, start))
|
||||
.Map(NormalizeGuideMode)
|
||||
.ToList();
|
||||
|
||||
effectiveBlocks.AddRange(newBlocks);
|
||||
@@ -56,5 +57,20 @@ internal record EffectiveBlock(Block Block, BlockKey BlockKey, DateTimeOffset St
|
||||
templateItem.StartTime.Hours,
|
||||
templateItem.StartTime.Minutes,
|
||||
0,
|
||||
start.Offset));
|
||||
start.Offset),
|
||||
templateItem.Id);
|
||||
|
||||
private static EffectiveBlock NormalizeGuideMode(EffectiveBlock effectiveBlock)
|
||||
{
|
||||
if (effectiveBlock.Block.Items is not null &&
|
||||
effectiveBlock.Block.Items.All(bi => bi.IncludeInProgramGuide == false))
|
||||
{
|
||||
foreach (BlockItem blockItem in effectiveBlock.Block.Items)
|
||||
{
|
||||
blockItem.IncludeInProgramGuide = true;
|
||||
}
|
||||
}
|
||||
|
||||
return effectiveBlock;
|
||||
}
|
||||
}
|
||||
|
||||
+4928
File diff suppressed because it is too large
Load Diff
+29
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Add_BlockItem_IncludeInProgramGuide : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IncludeInProgramGuide",
|
||||
table: "BlockItem",
|
||||
type: "tinyint(1)",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IncludeInProgramGuide",
|
||||
table: "BlockItem");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+4931
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Add_PlayoutItem_GuideStart : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "GuideStart",
|
||||
table: "PlayoutItem",
|
||||
type: "datetime(6)",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "GuideStart",
|
||||
table: "PlayoutItem");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1470,6 +1470,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
b.Property<int>("GuideGroup")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime?>("GuideStart")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<TimeSpan>("InPoint")
|
||||
.HasColumnType("time(6)");
|
||||
|
||||
@@ -1895,6 +1898,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
b.Property<int>("CollectionType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("IncludeInProgramGuide")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int>("Index")
|
||||
.HasColumnType("int");
|
||||
|
||||
|
||||
+4926
File diff suppressed because it is too large
Load Diff
+29
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Add_BlockItem_IncludeInProgramGuide : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IncludeInProgramGuide",
|
||||
table: "BlockItem",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IncludeInProgramGuide",
|
||||
table: "BlockItem");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+4929
File diff suppressed because it is too large
Load Diff
+29
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Add_PlayoutItem_GuideStart : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "GuideStart",
|
||||
table: "PlayoutItem",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "GuideStart",
|
||||
table: "PlayoutItem");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1468,6 +1468,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
b.Property<int>("GuideGroup")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("GuideStart")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<TimeSpan>("InPoint")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@@ -1893,6 +1896,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
b.Property<int>("CollectionType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IncludeInProgramGuide")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Index")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/CodeAnnotations/NamespacesWithAnnotations/=ErsatzTV_002EAnnotations/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DTO/@EntryIndexedValue">DTO</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EPG/@EntryIndexedValue">EPG</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FF/@EntryIndexedValue">FF</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HDHR/@EntryIndexedValue">HDHR</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LE/@EntryIndexedValue">LE</s:String>
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
<MudItem xs="8">
|
||||
<MudTable Class="mt-6" Hover="true" Items="_block.Items.OrderBy(i => i.Index)" Dense="true" @bind-SelectedItem="_selectedItem">
|
||||
<ColGroup>
|
||||
<col/>
|
||||
<col/>
|
||||
<col/>
|
||||
<col style="width: 60px;"/>
|
||||
@@ -66,6 +67,7 @@
|
||||
<HeaderContent>
|
||||
<MudTh>Collection</MudTh>
|
||||
<MudTh>Playback Order</MudTh>
|
||||
<MudTh>Show In EPG</MudTh>
|
||||
<MudTh/>
|
||||
<MudTh/>
|
||||
<MudTh/>
|
||||
@@ -82,6 +84,9 @@
|
||||
@context.PlaybackOrder
|
||||
</MudText>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudCheckBox T="bool" Value="@context.IncludeInProgramGuide" ValueChanged="@(e => UpdateEPG(context, e))" />
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ContentCopy"
|
||||
OnClick="@(_ => CopyItem(context))">
|
||||
@@ -390,7 +395,8 @@
|
||||
MultiCollection = item.MultiCollection,
|
||||
SmartCollection = item.SmartCollection,
|
||||
MediaItem = item.MediaItem,
|
||||
PlaybackOrder = item.PlaybackOrder
|
||||
PlaybackOrder = item.PlaybackOrder,
|
||||
IncludeInProgramGuide = item.IncludeInProgramGuide
|
||||
};
|
||||
|
||||
private void AddBlockItem()
|
||||
@@ -416,7 +422,8 @@
|
||||
Collection = item.Collection,
|
||||
MultiCollection = item.MultiCollection,
|
||||
SmartCollection = item.SmartCollection,
|
||||
MediaItem = item.MediaItem
|
||||
MediaItem = item.MediaItem,
|
||||
IncludeInProgramGuide = item.IncludeInProgramGuide
|
||||
};
|
||||
|
||||
foreach (BlockItemEditViewModel i in _block.Items.Filter(bi => bi.Index >= newItem.Index))
|
||||
@@ -473,7 +480,8 @@
|
||||
item.MultiCollection?.Id,
|
||||
item.SmartCollection?.Id,
|
||||
item.MediaItem?.MediaItemId,
|
||||
item.PlaybackOrder)).ToList();
|
||||
item.PlaybackOrder,
|
||||
item.IncludeInProgramGuide)).ToList();
|
||||
|
||||
_block.Minutes = _durationHours * 60 + _durationMinutes;
|
||||
|
||||
@@ -485,4 +493,9 @@
|
||||
_selectedItem = null;
|
||||
_previewItems = await Mediator.Send(new PreviewBlockPlayout(GenerateReplaceRequest()), _cts.Token);
|
||||
}
|
||||
|
||||
private static void UpdateEPG(BlockItemEditViewModel context, bool includeInProgramGuide)
|
||||
{
|
||||
context.IncludeInProgramGuide = includeInProgramGuide;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,8 @@ public class BlockItemEditViewModel : INotifyPropertyChanged
|
||||
};
|
||||
|
||||
public PlaybackOrder PlaybackOrder { get; set; }
|
||||
|
||||
public bool IncludeInProgramGuide { get; set; }
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user