Browse Source

load a configurable number of epg entries for text graphics (#2305)

* wip

* load a configurable number of epg entries for text graphics

* cleanup
pull/2306/head
Jason Dove 5 days ago committed by GitHub
parent
commit
2cb0d12701
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      CHANGELOG.md
  2. 3
      ErsatzTV.Core/Graphics/TextGraphicsElement.cs
  3. 5
      ErsatzTV.Core/Interfaces/Repositories/ITemplateDataRepository.cs
  4. 13
      ErsatzTV.Core/Metadata/EpgProgrammeTemplateData.cs
  5. 7
      ErsatzTV.Core/Metadata/EpgTemplateDataKey.cs
  6. 53
      ErsatzTV.Infrastructure/Data/Repositories/TemplateDataRepository.cs
  7. 51
      ErsatzTV.Infrastructure/Epg/EpgReader.cs
  8. 3
      ErsatzTV.Infrastructure/Streaming/GraphicsEngine.cs
  9. 2
      ErsatzTV.Infrastructure/Streaming/TextElement.cs

3
CHANGELOG.md

@ -26,7 +26,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -26,7 +26,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Displays multi-line text in a specified font, color, location, z-index
- Supports constant opacity and opacity expression
- Supports EPG and Media Item variable replacement
- EPG data is sourced from XMLTV
- EPG data is sourced from XMLTV for the current time
- EPG data can also load a configurable number of subsequent (up next) entries
- Media Item data is sourced from the currently playing media item
- Add `image` graphics element type
- Supported in playback troubleshooting and YAML playouts

3
ErsatzTV.Core/Graphics/TextGraphicsElement.cs

@ -33,6 +33,9 @@ public class TextGraphicsElement @@ -33,6 +33,9 @@ public class TextGraphicsElement
[YamlMember(Alias = "font_color", ApplyNamingConventions = false)]
public string FontColor { get; set; }
[YamlMember(Alias = "epg_entries", ApplyNamingConventions = false)]
public int EpgEntries { get; set; }
public string Text { get; set; }
public static async Task<Option<TextGraphicsElement>> FromFile(string fileName)

5
ErsatzTV.Core/Interfaces/Repositories/ITemplateDataRepository.cs

@ -6,5 +6,8 @@ public interface ITemplateDataRepository @@ -6,5 +6,8 @@ public interface ITemplateDataRepository
{
public Task<Option<Dictionary<string, object>>> GetMediaItemTemplateData(MediaItem mediaItem);
public Task<Option<Dictionary<string, object>>> GetEpgTemplateData(string channelNumber, DateTimeOffset time);
public Task<Option<Dictionary<string, object>>> GetEpgTemplateData(
string channelNumber,
DateTimeOffset time,
int count);
}

13
ErsatzTV.Core/Metadata/EpgProgrammeTemplateData.cs

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
namespace ErsatzTV.Core.Metadata;
public class EpgProgrammeTemplateData
{
public DateTime Start { get; set; }
public DateTime Stop { get; set; }
public string Title { get; set; }
public string SubTitle { get; set; }
public string Description { get; set; }
public string Rating { get; set; }
public string[] Categories { get; set; }
public string Date { get; set; }
}

7
ErsatzTV.Core/Metadata/EpgTemplateDataKey.cs

@ -2,10 +2,5 @@ namespace ErsatzTV.Core.Metadata; @@ -2,10 +2,5 @@ namespace ErsatzTV.Core.Metadata;
public static class EpgTemplateDataKey
{
public static readonly string Title = "Epg_Title";
public static readonly string SubTitle = "Epg_SubTitle";
public static readonly string Description = "Epg_Description";
public static readonly string Rating = "Epg_Rating";
public static readonly string Categories = "Epg_Categories";
public static readonly string Date = "Epg_Date";
public static readonly string Epg = "Epg";
}

53
ErsatzTV.Infrastructure/Data/Repositories/TemplateDataRepository.cs

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
using System.Globalization;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Extensions;
@ -22,7 +23,10 @@ public class TemplateDataRepository(ILocalFileSystem localFileSystem, IDbContext @@ -22,7 +23,10 @@ public class TemplateDataRepository(ILocalFileSystem localFileSystem, IDbContext
_ => Option<Dictionary<string, object>>.None
};
public async Task<Option<Dictionary<string, object>>> GetEpgTemplateData(string channelNumber, DateTimeOffset time)
public async Task<Option<Dictionary<string, object>>> GetEpgTemplateData(
string channelNumber,
DateTimeOffset time,
int count)
{
try
{
@ -30,19 +34,48 @@ public class TemplateDataRepository(ILocalFileSystem localFileSystem, IDbContext @@ -30,19 +34,48 @@ public class TemplateDataRepository(ILocalFileSystem localFileSystem, IDbContext
if (localFileSystem.FileExists(targetFile))
{
await using var stream = File.OpenRead(targetFile);
var maybeEpgProgramme = EpgReader.FindProgrammeAt(stream, time);
foreach (var epgProgramme in maybeEpgProgramme)
var xmlProgrammes = EpgReader.FindProgrammesAt(stream, time, count);
var result = new List<EpgProgrammeTemplateData>();
foreach (var epgProgramme in xmlProgrammes)
{
return new Dictionary<string, object>
var data = new EpgProgrammeTemplateData
{
[EpgTemplateDataKey.Title] = epgProgramme.Title?.Value,
[EpgTemplateDataKey.SubTitle] = epgProgramme.SubTitle?.Value,
[EpgTemplateDataKey.Description] = epgProgramme.Description?.Value,
[EpgTemplateDataKey.Rating] = epgProgramme.Rating?.Value,
[EpgTemplateDataKey.Categories] = (epgProgramme.Categories ?? []).Map(c => c.Value).ToArray(),
[EpgTemplateDataKey.Date] = epgProgramme.Date?.Value
Title = epgProgramme.Title?.Value,
SubTitle = epgProgramme.SubTitle?.Value,
Description = epgProgramme.Description?.Value,
Rating = epgProgramme.Rating?.Value,
Categories = (epgProgramme.Categories ?? []).Map(c => c.Value).ToArray(),
Date = epgProgramme.Date?.Value
};
if (DateTimeOffset.TryParseExact(
epgProgramme.Start,
EpgReader.XmlTvDateFormat,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out var start))
{
data.Start = start.LocalDateTime;
}
if (DateTimeOffset.TryParseExact(
epgProgramme.Stop,
EpgReader.XmlTvDateFormat,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out var stop))
{
data.Stop = stop.LocalDateTime;
}
result.Add(data);
}
return new Dictionary<string, object>
{
[EpgTemplateDataKey.Epg] = result
};
}
}
catch (Exception e)

51
ErsatzTV.Infrastructure/Epg/EpgReader.cs

@ -7,12 +7,13 @@ namespace ErsatzTV.Infrastructure.Epg; @@ -7,12 +7,13 @@ namespace ErsatzTV.Infrastructure.Epg;
public static class EpgReader
{
private const string XmlTvDateFormat = "yyyyMMddHHmmss zzz";
public const string XmlTvDateFormat = "yyyyMMddHHmmss zzz";
public static Option<EpgProgramme> FindProgrammeAt(Stream xmlStream, DateTimeOffset targetTime)
public static List<EpgProgramme> FindProgrammesAt(Stream xmlStream, DateTimeOffset targetTime, int count)
{
var serializer = new XmlSerializer(typeof(EpgProgramme));
var result = new List<EpgProgramme>();
var serializer = new XmlSerializer(typeof(EpgProgramme));
var settings = new XmlReaderSettings
{
ConformanceLevel = ConformanceLevel.Fragment
@ -20,13 +21,29 @@ public static class EpgReader @@ -20,13 +21,29 @@ public static class EpgReader
using var reader = XmlReader.Create(xmlStream, settings);
while (reader.Read())
var foundCurrent = false;
while (reader.Read() && count > 0)
{
if (reader.NodeType != XmlNodeType.Element || reader.Name != "programme")
{
continue;
}
if (foundCurrent)
{
using var subtreeReader = reader.ReadSubtree();
var maybeSubtreeProgramme = Optional(serializer.Deserialize(subtreeReader) as EpgProgramme);
result.AddRange(maybeSubtreeProgramme);
if (maybeSubtreeProgramme.IsNone)
{
return result;
}
count--;
continue;
}
string startStr = reader.GetAttribute("start");
string stopStr = reader.GetAttribute("stop");
@ -35,17 +52,35 @@ public static class EpgReader @@ -35,17 +52,35 @@ public static class EpgReader
continue;
}
if (DateTimeOffset.TryParseExact(startStr, XmlTvDateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var start) &&
DateTimeOffset.TryParseExact(stopStr, XmlTvDateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var stop))
if (DateTimeOffset.TryParseExact(
startStr,
XmlTvDateFormat,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out var start) &&
DateTimeOffset.TryParseExact(
stopStr,
XmlTvDateFormat,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out var stop))
{
if (start <= targetTime && targetTime < stop)
{
using var subtreeReader = reader.ReadSubtree();
return Optional(serializer.Deserialize(subtreeReader) as EpgProgramme);
var maybeCurrentProgramme = Optional(serializer.Deserialize(subtreeReader) as EpgProgramme);
result.AddRange(maybeCurrentProgramme);
if (maybeCurrentProgramme.IsNone)
{
return result;
}
foundCurrent = true;
count--;
}
}
}
return Option<EpgProgramme>.None;
return result;
}
}

3
ErsatzTV.Infrastructure/Streaming/GraphicsEngine.cs

@ -37,9 +37,10 @@ public class GraphicsEngine(ITemplateDataRepository templateDataRepository, ILog @@ -37,9 +37,10 @@ public class GraphicsEngine(ITemplateDataRepository templateDataRepository, ILog
}
// epg variables
int maxEpg = context.Elements.OfType<TextElementContext>().Max(c => c.TextElement.EpgEntries);
var startTime = context.ContentStartTime + context.Seek;
var maybeEpgData =
await templateDataRepository.GetEpgTemplateData(context.ChannelNumber, startTime);
await templateDataRepository.GetEpgTemplateData(context.ChannelNumber, startTime, maxEpg);
foreach (var templateData in maybeEpgData)
{
foreach (var variable in templateData)

2
ErsatzTV.Infrastructure/Streaming/TextElement.cs

@ -45,7 +45,7 @@ public class TextElement(TextGraphicsElement textElement, Dictionary<string, obj @@ -45,7 +45,7 @@ public class TextElement(TextGraphicsElement textElement, Dictionary<string, obj
ZIndex = textElement.ZIndex ?? 0;
string textToRender = await Template.Parse(textElement.Text).RenderAsync(variables);
string textToRender = await Template.Parse(textElement.Text).RenderAsync(variables, memberRenamer: member => member.Name);
var font = GraphicsEngineFonts.GetFont(textElement.FontFamily, textElement.FontSize ?? 48,
FontStyle.Regular);

Loading…
Cancel
Save