Browse Source

add graphics element name (#2613)

* add graphics element name

* update dependencies
pull/2614/head
Jason Dove 9 months ago committed by GitHub
parent
commit
7530c592ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 39
      ErsatzTV.Application/Graphics/Commands/RefreshGraphicsElementsHandler.cs
  3. 2
      ErsatzTV.Application/Graphics/GraphicsElementViewModel.cs
  4. 20
      ErsatzTV.Application/Graphics/Mapper.cs
  5. 2
      ErsatzTV.Application/Graphics/Queries/GetAllGraphicsElementsHandler.cs
  6. 1
      ErsatzTV.Core/Domain/GraphicsElement.cs
  7. 6
      ErsatzTV.Core/Graphics/BaseGraphicsElement.cs
  8. 2
      ErsatzTV.Core/Graphics/ImageGraphicsElement.cs
  9. 2
      ErsatzTV.Core/Graphics/MotionGraphicsElement.cs
  10. 2
      ErsatzTV.Core/Graphics/SubtitleGraphicsElement.cs
  11. 2
      ErsatzTV.Core/Graphics/TextGraphicsElement.cs
  12. 2
      ErsatzTV.Core/Interfaces/Streaming/IGraphicsElementLoader.cs
  13. 2
      ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj
  14. 6867
      ErsatzTV.Infrastructure.MySql/Migrations/20251108120730_Add_GraphicsElementName.Designer.cs
  15. 29
      ErsatzTV.Infrastructure.MySql/Migrations/20251108120730_Add_GraphicsElementName.cs
  16. 3
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  17. 6694
      ErsatzTV.Infrastructure.Sqlite/Migrations/20251108120658_Add_GraphicsElementName.Designer.cs
  18. 28
      ErsatzTV.Infrastructure.Sqlite/Migrations/20251108120658_Add_GraphicsElementName.cs
  19. 3
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  20. 54
      ErsatzTV.Infrastructure/Streaming/Graphics/GraphicsElementLoader.cs
  21. 4
      ErsatzTV/ErsatzTV.csproj

1
CHANGELOG.md

@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Add `MediaItem_Stop` template data (DateTimeOffset)
- Add `ScaledResolution` template data (the final size of the frame before padding)
- Add `place_within_source_content` (true/false) field to image graphics element
- Add `name` field to all graphics elements to display in the UI
- Classic schedules: add collection type `Search Query`
- This allows defining search queries directly on schedule items without creating smart collections beforehand
- As an example, this can be used to filter or combine existing smart collections

39
ErsatzTV.Application/Graphics/Commands/RefreshGraphicsElementsHandler.cs

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Streaming;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
@ -10,6 +11,7 @@ namespace ErsatzTV.Application.Graphics; @@ -10,6 +11,7 @@ namespace ErsatzTV.Application.Graphics;
public class RefreshGraphicsElementsHandler(
IDbContextFactory<TvContext> dbContextFactory,
ILocalFileSystem localFileSystem,
IGraphicsElementLoader graphicsElementLoader,
ILogger<RefreshGraphicsElementsHandler> logger)
: IRequestHandler<RefreshGraphicsElements>
{
@ -21,7 +23,11 @@ public class RefreshGraphicsElementsHandler( @@ -21,7 +23,11 @@ public class RefreshGraphicsElementsHandler(
List<GraphicsElement> allExisting = await dbContext.GraphicsElements
.ToListAsync(cancellationToken);
foreach (GraphicsElement existing in allExisting.Where(e => !localFileSystem.FileExists(e.Path)))
var missing = allExisting
.Where(e => !localFileSystem.FileExists(e.Path) || Path.GetExtension(e.Path) != ".yml")
.ToList();
foreach (GraphicsElement existing in missing)
{
logger.LogWarning(
"Removing graphics element that references non-existing file {File}",
@ -30,8 +36,13 @@ public class RefreshGraphicsElementsHandler( @@ -30,8 +36,13 @@ public class RefreshGraphicsElementsHandler(
dbContext.GraphicsElements.Remove(existing);
}
foreach (GraphicsElement existing in allExisting.Except(missing))
{
await TryRefreshName(existing, cancellationToken);
}
// add new text elements
var newTextPaths = localFileSystem.ListFiles(FileSystemLayout.GraphicsElementsTextTemplatesFolder)
var newTextPaths = localFileSystem.ListFiles(FileSystemLayout.GraphicsElementsTextTemplatesFolder, "*.yml")
.Where(f => allExisting.All(e => e.Path != f))
.ToList();
@ -45,11 +56,13 @@ public class RefreshGraphicsElementsHandler( @@ -45,11 +56,13 @@ public class RefreshGraphicsElementsHandler(
Kind = GraphicsElementKind.Text
};
await TryRefreshName(graphicsElement, cancellationToken);
await dbContext.AddAsync(graphicsElement, cancellationToken);
}
// add new image elements
var newImagePaths = localFileSystem.ListFiles(FileSystemLayout.GraphicsElementsImageTemplatesFolder)
var newImagePaths = localFileSystem.ListFiles(FileSystemLayout.GraphicsElementsImageTemplatesFolder, "*.yml")
.Where(f => allExisting.All(e => e.Path != f))
.ToList();
@ -63,11 +76,13 @@ public class RefreshGraphicsElementsHandler( @@ -63,11 +76,13 @@ public class RefreshGraphicsElementsHandler(
Kind = GraphicsElementKind.Image
};
await TryRefreshName(graphicsElement, cancellationToken);
await dbContext.AddAsync(graphicsElement, cancellationToken);
}
// add new motion elements
var newMotionPaths = localFileSystem.ListFiles(FileSystemLayout.GraphicsElementsMotionTemplatesFolder)
var newMotionPaths = localFileSystem.ListFiles(FileSystemLayout.GraphicsElementsMotionTemplatesFolder, "*.yml")
.Where(f => allExisting.All(e => e.Path != f))
.ToList();
@ -81,11 +96,13 @@ public class RefreshGraphicsElementsHandler( @@ -81,11 +96,13 @@ public class RefreshGraphicsElementsHandler(
Kind = GraphicsElementKind.Motion
};
await TryRefreshName(graphicsElement, cancellationToken);
await dbContext.AddAsync(graphicsElement, cancellationToken);
}
// add new subtitle elements
var newSubtitlePaths = localFileSystem.ListFiles(FileSystemLayout.GraphicsElementsSubtitleTemplatesFolder)
var newSubtitlePaths = localFileSystem.ListFiles(FileSystemLayout.GraphicsElementsSubtitleTemplatesFolder, "*.yml")
.Where(f => allExisting.All(e => e.Path != f))
.ToList();
@ -99,9 +116,21 @@ public class RefreshGraphicsElementsHandler( @@ -99,9 +116,21 @@ public class RefreshGraphicsElementsHandler(
Kind = GraphicsElementKind.Subtitle
};
await TryRefreshName(graphicsElement, cancellationToken);
await dbContext.AddAsync(graphicsElement, cancellationToken);
}
await dbContext.SaveChangesAsync(cancellationToken);
}
private async Task TryRefreshName(GraphicsElement graphicsElement, CancellationToken cancellationToken)
{
graphicsElement.Name = null;
Option<string> maybeName = await graphicsElementLoader.TryLoadName(graphicsElement.Path, cancellationToken);
foreach (string name in maybeName)
{
graphicsElement.Name = name;
}
}
}

2
ErsatzTV.Application/Graphics/GraphicsElementViewModel.cs

@ -1,3 +1,3 @@ @@ -1,3 +1,3 @@
namespace ErsatzTV.Application.Graphics;
public record GraphicsElementViewModel(int Id, string Name);
public record GraphicsElementViewModel(int Id, string Name, string FileName);

20
ErsatzTV.Application/Graphics/Mapper.cs

@ -7,13 +7,21 @@ public static class Mapper @@ -7,13 +7,21 @@ public static class Mapper
public static GraphicsElementViewModel ProjectToViewModel(GraphicsElement graphicsElement)
{
string fileName = Path.GetFileName(graphicsElement.Path);
return graphicsElement.Kind switch
fileName = graphicsElement.Kind switch
{
GraphicsElementKind.Text => new GraphicsElementViewModel(graphicsElement.Id, $"text/{fileName}"),
GraphicsElementKind.Image => new GraphicsElementViewModel(graphicsElement.Id, $"image/{fileName}"),
GraphicsElementKind.Subtitle => new GraphicsElementViewModel(graphicsElement.Id, $"subtitle/{fileName}"),
GraphicsElementKind.Motion => new GraphicsElementViewModel(graphicsElement.Id, $"motion/{fileName}"),
_ => new GraphicsElementViewModel(graphicsElement.Id, graphicsElement.Path)
GraphicsElementKind.Text => $"text/{fileName}",
GraphicsElementKind.Image => $"image/{fileName}",
GraphicsElementKind.Subtitle => $"subtitle/{fileName}",
GraphicsElementKind.Motion => $"motion/{fileName}",
_ => graphicsElement.Path
};
string name = fileName;
if (!string.IsNullOrWhiteSpace(graphicsElement.Name))
{
name = $"{graphicsElement.Name} ({fileName})";
}
return new GraphicsElementViewModel(graphicsElement.Id, name, fileName);
}
}

2
ErsatzTV.Application/Graphics/Queries/GetAllGraphicsElementsHandler.cs

@ -14,6 +14,6 @@ public class GetAllGraphicsElementsHandler(IDbContextFactory<TvContext> dbContex @@ -14,6 +14,6 @@ public class GetAllGraphicsElementsHandler(IDbContextFactory<TvContext> dbContex
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
return await dbContext.GraphicsElements
.ToListAsync(cancellationToken)
.Map(list => list.Map(ProjectToViewModel).ToList());
.Map(list => list.Map(ProjectToViewModel).OrderBy(e => e.Name == e.FileName).ThenBy(e => e.Name).ToList());
}
}

1
ErsatzTV.Core/Domain/GraphicsElement.cs

@ -6,6 +6,7 @@ public class GraphicsElement @@ -6,6 +6,7 @@ public class GraphicsElement
{
public int Id { get; set; }
public string Path { get; set; }
public string Name { get; set; }
public GraphicsElementKind Kind { get; set; }
public List<PlayoutItem> PlayoutItems { get; set; }
public List<PlayoutItemGraphicsElement> PlayoutItemGraphicsElements { get; set; }

6
ErsatzTV.Core/Graphics/BaseGraphicsElement.cs

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
namespace ErsatzTV.Core.Graphics;
public class BaseGraphicsElement
{
public string Name { get; set; }
}

2
ErsatzTV.Core/Graphics/ImageGraphicsElement.cs

@ -3,7 +3,7 @@ using YamlDotNet.Serialization; @@ -3,7 +3,7 @@ using YamlDotNet.Serialization;
namespace ErsatzTV.Core.Graphics;
public class ImageGraphicsElement
public class ImageGraphicsElement : BaseGraphicsElement
{
public string Image { get; set; }

2
ErsatzTV.Core/Graphics/MotionGraphicsElement.cs

@ -3,7 +3,7 @@ using YamlDotNet.Serialization; @@ -3,7 +3,7 @@ using YamlDotNet.Serialization;
namespace ErsatzTV.Core.Graphics;
public class MotionGraphicsElement
public class MotionGraphicsElement : BaseGraphicsElement
{
[YamlMember(Alias = "video_path", ApplyNamingConventions = false)]
public string VideoPath { get; set; }

2
ErsatzTV.Core/Graphics/SubtitleGraphicsElement.cs

@ -2,7 +2,7 @@ using YamlDotNet.Serialization; @@ -2,7 +2,7 @@ using YamlDotNet.Serialization;
namespace ErsatzTV.Core.Graphics;
public class SubtitleGraphicsElement
public class SubtitleGraphicsElement : BaseGraphicsElement
{
[YamlMember(Alias = "z_index", ApplyNamingConventions = false)]
public int? ZIndex { get; set; }

2
ErsatzTV.Core/Graphics/TextGraphicsElement.cs

@ -3,7 +3,7 @@ using YamlDotNet.Serialization; @@ -3,7 +3,7 @@ using YamlDotNet.Serialization;
namespace ErsatzTV.Core.Graphics;
public class TextGraphicsElement
public class TextGraphicsElement : BaseGraphicsElement
{
[YamlMember(Alias = "opacity_percent", ApplyNamingConventions = false)]
public int? OpacityPercent { get; set; }

2
ErsatzTV.Core/Interfaces/Streaming/IGraphicsElementLoader.cs

@ -8,4 +8,6 @@ public interface IGraphicsElementLoader @@ -8,4 +8,6 @@ public interface IGraphicsElementLoader
GraphicsEngineContext context,
List<PlayoutItemGraphicsElement> elements,
CancellationToken cancellationToken);
Task<Option<string>> TryLoadName(string fileName, CancellationToken cancellationToken);
}

2
ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj

@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
<ItemGroup>
<PackageReference Include="CliWrap" Version="3.9.0" />
<PackageReference Include="Hardware.Info" Version="101.1.0" />
<PackageReference Include="Hardware.Info" Version="101.1.0.1" />
<PackageReference Include="LanguageExt.Core" Version="4.4.9" />
<PackageReference Include="Lennox.NvEncSharp" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.10" />

6867
ErsatzTV.Infrastructure.MySql/Migrations/20251108120730_Add_GraphicsElementName.Designer.cs generated

File diff suppressed because it is too large Load Diff

29
ErsatzTV.Infrastructure.MySql/Migrations/20251108120730_Add_GraphicsElementName.cs

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Add_GraphicsElementName : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Name",
table: "GraphicsElement",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Name",
table: "GraphicsElement");
}
}
}

3
ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs

@ -934,6 +934,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -934,6 +934,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<int>("Kind")
.HasColumnType("int");
b.Property<string>("Name")
.HasColumnType("longtext");
b.Property<string>("Path")
.HasColumnType("longtext");

6694
ErsatzTV.Infrastructure.Sqlite/Migrations/20251108120658_Add_GraphicsElementName.Designer.cs generated

File diff suppressed because it is too large Load Diff

28
ErsatzTV.Infrastructure.Sqlite/Migrations/20251108120658_Add_GraphicsElementName.cs

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Add_GraphicsElementName : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Name",
table: "GraphicsElement",
type: "TEXT",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Name",
table: "GraphicsElement");
}
}
}

3
ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs

@ -897,6 +897,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -897,6 +897,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<int>("Kind")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("Path")
.HasColumnType("TEXT");

54
ErsatzTV.Infrastructure/Streaming/Graphics/GraphicsElementLoader.cs

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
using System.Text;
using System.Text.RegularExpressions;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Graphics;
@ -9,6 +10,7 @@ using Microsoft.Extensions.Logging; @@ -9,6 +10,7 @@ using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Scriban;
using Scriban.Runtime;
using Scriban.Syntax;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
@ -131,6 +133,41 @@ public partial class GraphicsElementLoader( @@ -131,6 +133,41 @@ public partial class GraphicsElementLoader(
return context;
}
public async Task<Option<string>> TryLoadName(string fileName, CancellationToken cancellationToken)
{
try
{
string yaml = await localFileSystem.ReadAllText(fileName);
var template = Template.Parse(yaml);
var builder = new StringBuilder();
var scriptPage = template.Page;
if (scriptPage.Body != null)
{
foreach (var statement in scriptPage.Body.Statements)
{
if (statement is ScriptRawStatement rawStatement)
{
builder.Append(rawStatement.Text);
}
}
}
Option<BaseGraphicsElement> maybeElement = FromYamlIgnoreUnmatched<BaseGraphicsElement>(builder.ToString());
foreach (BaseGraphicsElement element in maybeElement.Where(e => !string.IsNullOrWhiteSpace(e.Name)))
{
return element.Name;
}
}
catch (Exception)
{
// do nothing
}
return Option<string>.None;
}
private async Task<int> GetMaxEpgEntries(List<PlayoutItemGraphicsElement> elements)
{
var epgEntries = 0;
@ -254,6 +291,23 @@ public partial class GraphicsElementLoader( @@ -254,6 +291,23 @@ public partial class GraphicsElementLoader(
}
}
private static Option<T> FromYamlIgnoreUnmatched<T>(string yaml)
{
try
{
IDeserializer deserializer = new DeserializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.IgnoreUnmatchedProperties()
.Build();
return deserializer.Deserialize<T>(yaml);
}
catch (Exception)
{
return Option<T>.None;
}
}
[GeneratedRegex(@"epg_entries:\s*(\d+)")]
private static partial Regex EpgEntriesRegex();
}

4
ErsatzTV/ErsatzTV.csproj

@ -28,7 +28,7 @@ @@ -28,7 +28,7 @@
<ItemGroup>
<PackageReference Include="Blazored.FluentValidation" Version="2.2.0" />
<PackageReference Include="BlazorSortable" Version="5.1.3" />
<PackageReference Include="BlazorSortable" Version="5.1.4" />
<PackageReference Include="Bugsnag.AspNet.Core" Version="4.1.0" />
<PackageReference Include="FluentValidation" Version="12.1.0" />
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.1" />
@ -54,7 +54,7 @@ @@ -54,7 +54,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="MudBlazor" Version="8.13.0" />
<PackageReference Include="MudBlazor" Version="8.14.0" />
<PackageReference Include="NaturalSort.Extension" Version="4.4.0" />
<PackageReference Include="Refit.HttpClientFactory" Version="8.0.0" />
<PackageReference Include="Scalar.AspNetCore" Version="2.10.1" />

Loading…
Cancel
Save