Browse Source

add music video credits template system (#982)

* add music video credits template system

* fix search index bug

* cleanup csproj
pull/983/head
Jason Dove 3 years ago committed by GitHub
parent
commit
6035c10550
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      CHANGELOG.md
  2. 3
      ErsatzTV.Application/Channels/ChannelViewModel.cs
  3. 3
      ErsatzTV.Application/Channels/Commands/CreateChannel.cs
  4. 3
      ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs
  5. 3
      ErsatzTV.Application/Channels/Commands/UpdateChannel.cs
  6. 1
      ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs
  7. 3
      ErsatzTV.Application/Channels/Mapper.cs
  8. 1
      ErsatzTV.Application/ErsatzTV.Application.csproj.DotSettings
  9. 34
      ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs
  10. 3
      ErsatzTV.Application/Templates/Queries/GetMusicVideoCreditTemplates.cs
  11. 20
      ErsatzTV.Application/Templates/Queries/GetMusicVideoCreditTemplatesHandler.cs
  12. 1
      ErsatzTV.Core/Domain/Channel.cs
  13. 3
      ErsatzTV.Core/Domain/ChannelMusicVideoCreditsMode.cs
  14. 3
      ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs
  15. 5
      ErsatzTV.Core/FileSystemLayout.cs
  16. 5
      ErsatzTV.Core/Interfaces/FFmpeg/IMusicVideoCreditsGenerator.cs
  17. 2
      ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs
  18. 1
      ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj
  19. 66
      ErsatzTV.Infrastructure/FFmpeg/MusicVideoCreditsGenerator.cs
  20. 4309
      ErsatzTV.Infrastructure/Migrations/20221007192809_Add_ChannelMusicVideoCreditsTemplate.Designer.cs
  21. 25
      ErsatzTV.Infrastructure/Migrations/20221007192809_Add_ChannelMusicVideoCreditsTemplate.cs
  22. 3
      ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs
  23. 5
      ErsatzTV/ErsatzTV.csproj
  24. 19
      ErsatzTV/Pages/ChannelEditor.razor
  25. 13
      ErsatzTV/Resources/Templates/_default.ass.sbntxt
  26. 30
      ErsatzTV/Services/RunOnce/ResourceExtractorService.cs
  27. 11
      ErsatzTV/Startup.cs
  28. 15
      ErsatzTV/ViewModels/ChannelEditViewModel.cs

18
CHANGELOG.md

@ -8,6 +8,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -8,6 +8,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix bug where tail or fallback filler would sometimes schedule much longer than expected
- This only happened with fixed start schedule items following a schedule item with tail or fallback filler
### Added
- Add music video credits template system
- Templates are selected in each channel's settings
- Templates should be copied from `_default.ass.sbntxt` which is located in the config subfolder `templates/music-video-credits`
- Copy the file, give it any name ending with `.ass.sbntext`, and only make edits to the copied file
- The default template will be extracted and overwritten every time ErsatzTV is started
- The template is a [Sub Station Alpha](http://www.tcax.org/docs/ass-specs.htm) file using [scribian](https://github.com/scriban/scriban/tree/master/doc) template syntax
- The following fields are available for use in the template:
- `resolution`: the ffmpeg profile's resolution, which is used for margin calculations
- `title`: the title of the music video
- `track`: the music video's track number
- `album`: the music video's album
- `plot`: the music video's plot
- `release_date`: the music video's release date
- `artist`: the music videos artist (the parent folder)
- `all_artists`: a list of additional artists from the music video's sidecar NFO metadata file
- `duration`: the timespan duration of the music video, which can be used to calculate timing of additional subtitles
## [0.6.8-beta] - 2022-10-05
### Fixed
- Fix typo introduced in `0.6.7-beta` that stopped QSV HEVC encoder from working

3
ErsatzTV.Application/Channels/ChannelViewModel.cs

@ -18,4 +18,5 @@ public record ChannelViewModel( @@ -18,4 +18,5 @@ public record ChannelViewModel(
int PlayoutCount,
string PreferredSubtitleLanguageCode,
ChannelSubtitleMode SubtitleMode,
ChannelMusicVideoCreditsMode MusicVideoCreditsMode);
ChannelMusicVideoCreditsMode MusicVideoCreditsMode,
string MusicVideoCreditsTemplate);

3
ErsatzTV.Application/Channels/Commands/CreateChannel.cs

@ -18,4 +18,5 @@ public record CreateChannel @@ -18,4 +18,5 @@ public record CreateChannel
int? FallbackFillerId,
string PreferredSubtitleLanguageCode,
ChannelSubtitleMode SubtitleMode,
ChannelMusicVideoCreditsMode MusicVideoCreditsMode) : IRequest<Either<BaseError, CreateChannelResult>>;
ChannelMusicVideoCreditsMode MusicVideoCreditsMode,
string MusicVideoCreditsTemplate) : IRequest<Either<BaseError, CreateChannelResult>>;

3
ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs

@ -74,7 +74,8 @@ public class CreateChannelHandler : IRequestHandler<CreateChannel, Either<BaseEr @@ -74,7 +74,8 @@ public class CreateChannelHandler : IRequestHandler<CreateChannel, Either<BaseEr
PreferredAudioTitle = request.PreferredAudioTitle,
PreferredSubtitleLanguageCode = preferredSubtitleLanguageCode,
SubtitleMode = request.SubtitleMode,
MusicVideoCreditsMode = request.MusicVideoCreditsMode
MusicVideoCreditsMode = request.MusicVideoCreditsMode,
MusicVideoCreditsTemplate = request.MusicVideoCreditsTemplate
};
foreach (int id in watermarkId)

3
ErsatzTV.Application/Channels/Commands/UpdateChannel.cs

@ -19,4 +19,5 @@ public record UpdateChannel @@ -19,4 +19,5 @@ public record UpdateChannel
int? FallbackFillerId,
string PreferredSubtitleLanguageCode,
ChannelSubtitleMode SubtitleMode,
ChannelMusicVideoCreditsMode MusicVideoCreditsMode) : IRequest<Either<BaseError, ChannelViewModel>>;
ChannelMusicVideoCreditsMode MusicVideoCreditsMode,
string MusicVideoCreditsTemplate) : IRequest<Either<BaseError, ChannelViewModel>>;

1
ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs

@ -46,6 +46,7 @@ public class UpdateChannelHandler : IRequestHandler<UpdateChannel, Either<BaseEr @@ -46,6 +46,7 @@ public class UpdateChannelHandler : IRequestHandler<UpdateChannel, Either<BaseEr
c.PreferredSubtitleLanguageCode = update.PreferredSubtitleLanguageCode;
c.SubtitleMode = update.SubtitleMode;
c.MusicVideoCreditsMode = update.MusicVideoCreditsMode;
c.MusicVideoCreditsTemplate = update.MusicVideoCreditsTemplate;
c.Artwork ??= new List<Artwork>();
if (!string.IsNullOrWhiteSpace(update.Logo))

3
ErsatzTV.Application/Channels/Mapper.cs

@ -22,7 +22,8 @@ internal static class Mapper @@ -22,7 +22,8 @@ internal static class Mapper
channel.Playouts?.Count ?? 0,
channel.PreferredSubtitleLanguageCode,
channel.SubtitleMode,
channel.MusicVideoCreditsMode);
channel.MusicVideoCreditsMode,
channel.MusicVideoCreditsTemplate);
internal static ChannelResponseModel ProjectToResponseModel(Channel channel) =>
new(

1
ErsatzTV.Application/ErsatzTV.Application.csproj.DotSettings

@ -40,5 +40,6 @@ @@ -40,5 +40,6 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=streaming_005Cqueries/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=subtitles_005Ccommands/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=television_005Cqueries/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=templates_005Cqueries/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=watermarks_005Ccommands/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=watermarks_005Cqueries/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

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

@ -324,18 +324,30 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -324,18 +324,30 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
{
var subtitles = new List<Subtitle>();
bool musicVideoCredits = channel.MusicVideoCreditsMode == ChannelMusicVideoCreditsMode.GenerateSubtitles;
if (musicVideoCredits)
switch (channel.MusicVideoCreditsMode)
{
subtitles.AddRange(
await _musicVideoCreditsGenerator.GenerateCreditsSubtitle(musicVideo, channel.FFmpegProfile));
}
else
{
subtitles.AddRange(
await Optional(musicVideo.MusicVideoMetadata).Flatten().HeadOrNone()
.Map(mm => mm.Subtitles)
.IfNoneAsync(new List<Subtitle>()));
case ChannelMusicVideoCreditsMode.GenerateSubtitles:
subtitles.AddRange(
await _musicVideoCreditsGenerator.GenerateCreditsSubtitle(musicVideo, channel.FFmpegProfile));
break;
case ChannelMusicVideoCreditsMode.TemplateSubtitles:
var fileWithExtension = $"{channel.MusicVideoCreditsTemplate}.sbntxt";
if (!string.IsNullOrWhiteSpace(fileWithExtension))
{
subtitles.AddRange(
await _musicVideoCreditsGenerator.GenerateCreditsSubtitleFromTemplate(
musicVideo,
channel.FFmpegProfile,
Path.Combine(FileSystemLayout.MusicVideoCreditsTemplatesFolder, fileWithExtension)));
}
break;
case ChannelMusicVideoCreditsMode.None:
default:
subtitles.AddRange(
await Optional(musicVideo.MusicVideoMetadata).Flatten().HeadOrNone()
.Map(mm => mm.Subtitles)
.IfNoneAsync(new List<Subtitle>()));
break;
}
return subtitles;

3
ErsatzTV.Application/Templates/Queries/GetMusicVideoCreditTemplates.cs

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
namespace ErsatzTV.Application.Templates;
public record GetMusicVideoCreditTemplates : IRequest<List<string>>;

20
ErsatzTV.Application/Templates/Queries/GetMusicVideoCreditTemplatesHandler.cs

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Interfaces.Metadata;
namespace ErsatzTV.Application.Templates;
public class GetMusicVideoCreditTemplatesHandler : IRequestHandler<GetMusicVideoCreditTemplates, List<string>>
{
private readonly ILocalFileSystem _localFileSystem;
public GetMusicVideoCreditTemplatesHandler(ILocalFileSystem localFileSystem)
{
_localFileSystem = localFileSystem;
}
public Task<List<string>> Handle(GetMusicVideoCreditTemplates request, CancellationToken cancellationToken) =>
_localFileSystem.ListFiles(FileSystemLayout.MusicVideoCreditsTemplatesFolder)
.Map(Path.GetFileNameWithoutExtension)
.ToList()
.AsTask();
}

1
ErsatzTV.Core/Domain/Channel.cs

@ -27,4 +27,5 @@ public class Channel @@ -27,4 +27,5 @@ public class Channel
public string PreferredSubtitleLanguageCode { get; set; }
public ChannelSubtitleMode SubtitleMode { get; set; }
public ChannelMusicVideoCreditsMode MusicVideoCreditsMode { get; set; }
public string MusicVideoCreditsTemplate { get; set; }
}

3
ErsatzTV.Core/Domain/ChannelMusicVideoCreditsMode.cs

@ -3,5 +3,6 @@ @@ -3,5 +3,6 @@
public enum ChannelMusicVideoCreditsMode
{
None = 0,
GenerateSubtitles = 1
GenerateSubtitles = 1,
TemplateSubtitles = 2
}

3
ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs

@ -92,7 +92,8 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector @@ -92,7 +92,8 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector
string preferredSubtitleLanguage,
ChannelSubtitleMode subtitleMode)
{
if (channel.MusicVideoCreditsMode == ChannelMusicVideoCreditsMode.GenerateSubtitles &&
if (channel.MusicVideoCreditsMode is ChannelMusicVideoCreditsMode.GenerateSubtitles
or ChannelMusicVideoCreditsMode.TemplateSubtitles &&
subtitles.FirstOrDefault(s => s.SubtitleKind == SubtitleKind.Generated) is { } generatedSubtitle)
{
_logger.LogDebug("Selecting generated subtitle for channel {Number}", channel.Number);

5
ErsatzTV.Core/FileSystemLayout.cs

@ -45,4 +45,9 @@ public static class FileSystemLayout @@ -45,4 +45,9 @@ public static class FileSystemLayout
public static readonly string SubtitleCacheFolder = Path.Combine(StreamsCacheFolder, "subtitles");
public static readonly string FontsCacheFolder = Path.Combine(StreamsCacheFolder, "fonts");
public static readonly string TemplatesFolder = Path.Combine(AppDataFolder, "templates");
public static readonly string MusicVideoCreditsTemplatesFolder =
Path.Combine(TemplatesFolder, "music-video-credits");
}

5
ErsatzTV.Core/Interfaces/FFmpeg/IMusicVideoCreditsGenerator.cs

@ -5,4 +5,9 @@ namespace ErsatzTV.Core.Interfaces.FFmpeg; @@ -5,4 +5,9 @@ namespace ErsatzTV.Core.Interfaces.FFmpeg;
public interface IMusicVideoCreditsGenerator
{
Task<Option<Subtitle>> GenerateCreditsSubtitle(MusicVideo musicVideo, FFmpegProfile ffmpegProfile);
Task<Option<Subtitle>> GenerateCreditsSubtitleFromTemplate(
MusicVideo musicVideo,
FFmpegProfile ffmpegProfile,
string templateFileName);
}

2
ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs

@ -69,6 +69,8 @@ public class SearchRepository : ISearchRepository @@ -69,6 +69,8 @@ public class SearchRepository : ISearchRepository
.ThenInclude(sm => sm.Studios)
.Include(mi => (mi as Season).SeasonMetadata)
.ThenInclude(sm => sm.Actors)
.Include(mi => (mi as Season).SeasonMetadata)
.ThenInclude(sm => sm.Guids)
.Include(mi => (mi as Season).Show)
.ThenInclude(sm => sm.ShowMetadata)
.ThenInclude(sm => sm.Genres)

1
ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj

@ -27,6 +27,7 @@ @@ -27,6 +27,7 @@
<PackageReference Include="Refit" Version="6.3.2" />
<PackageReference Include="Refit.Newtonsoft.Json" Version="6.3.2" />
<PackageReference Include="Refit.Xml" Version="6.3.2" />
<PackageReference Include="Scriban.Signed" Version="5.5.0" />
</ItemGroup>
<ItemGroup>

66
ErsatzTV.Core/FFmpeg/MusicVideoCreditsGenerator.cs → ErsatzTV.Infrastructure/FFmpeg/MusicVideoCreditsGenerator.cs

@ -1,14 +1,23 @@ @@ -1,14 +1,23 @@
using System.Text;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.FFmpeg;
using ErsatzTV.Core.Interfaces.FFmpeg;
using Microsoft.Extensions.Logging;
using Scriban;
namespace ErsatzTV.Core.FFmpeg;
namespace ErsatzTV.Infrastructure.FFmpeg;
public class MusicVideoCreditsGenerator : IMusicVideoCreditsGenerator
{
private readonly ITempFilePool _tempFilePool;
private readonly ILogger<MusicVideoCreditsGenerator> _logger;
public MusicVideoCreditsGenerator(ITempFilePool tempFilePool) => _tempFilePool = tempFilePool;
public MusicVideoCreditsGenerator(ITempFilePool tempFilePool, ILogger<MusicVideoCreditsGenerator> logger)
{
_tempFilePool = tempFilePool;
_logger = logger;
}
public async Task<Option<Subtitle>> GenerateCreditsSubtitle(MusicVideo musicVideo, FFmpegProfile ffmpegProfile)
{
@ -81,4 +90,57 @@ public class MusicVideoCreditsGenerator : IMusicVideoCreditsGenerator @@ -81,4 +90,57 @@ public class MusicVideoCreditsGenerator : IMusicVideoCreditsGenerator
return None;
}
public async Task<Option<Subtitle>> GenerateCreditsSubtitleFromTemplate(
MusicVideo musicVideo,
FFmpegProfile ffmpegProfile,
string templateFileName)
{
try
{
string text = await File.ReadAllTextAsync(templateFileName);
var template = Template.Parse(text, templateFileName);
foreach (MusicVideoMetadata metadata in musicVideo.MusicVideoMetadata)
{
string artist = string.Empty;
foreach (ArtistMetadata artistMetadata in Optional(musicVideo.Artist?.ArtistMetadata).Flatten())
{
artist = artistMetadata.Title;
}
string result = await template.RenderAsync(
new
{
ffmpegProfile.Resolution,
metadata.Title,
metadata.Track,
metadata.Album,
metadata.Plot,
metadata.ReleaseDate,
AllArtists = (metadata.Artists ?? new List<MusicVideoArtist>()).Map(a => a.Name),
Artist = artist,
musicVideo.GetHeadVersion().Duration
});
string fileName = _tempFilePool.GetNextTempFile(TempFileCategory.Subtitle);
await File.WriteAllTextAsync(fileName, result);
return new Subtitle
{
Codec = "ass",
Default = true,
Forced = true,
IsExtracted = false,
SubtitleKind = SubtitleKind.Generated,
Path = fileName,
SDH = false
};
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error generating music video credits from template {Template}", templateFileName);
}
return None;
}
}

4309
ErsatzTV.Infrastructure/Migrations/20221007192809_Add_ChannelMusicVideoCreditsTemplate.Designer.cs generated

File diff suppressed because it is too large Load Diff

25
ErsatzTV.Infrastructure/Migrations/20221007192809_Add_ChannelMusicVideoCreditsTemplate.cs

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

3
ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs

@ -236,6 +236,9 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -236,6 +236,9 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Property<int>("MusicVideoCreditsMode")
.HasColumnType("INTEGER");
b.Property<string>("MusicVideoCreditsTemplate")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");

5
ErsatzTV/ErsatzTV.csproj

@ -50,10 +50,6 @@ @@ -50,10 +50,6 @@
<Exec WorkingDirectory="$(SpaRoot)" Command="npm ci" />
</Target>
<ItemGroup>
<Folder Include="Resources" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Bugsnag.AspNet.Core" Version="3.1.0" />
@ -109,6 +105,7 @@ @@ -109,6 +105,7 @@
<EmbeddedResource Include="Resources\Roboto-Regular.ttf" />
<EmbeddedResource Include="Resources\OPTIKabel-Heavy.otf" />
<EmbeddedResource Include="Resources\ISO-639-2_utf-8.txt" />
<EmbeddedResource Include="Resources\Templates\_default.ass.sbntxt" />
</ItemGroup>
<ItemGroup>

19
ErsatzTV/Pages/ChannelEditor.razor

@ -9,6 +9,7 @@ @@ -9,6 +9,7 @@
@using System.Globalization
@using ErsatzTV.Core.Domain.Filler
@using ErsatzTV.Application.Channels
@using ErsatzTV.Application.Templates
@implements IDisposable
@inject NavigationManager NavigationManager
@inject ILogger<ChannelEditor> Logger
@ -75,6 +76,18 @@ @@ -75,6 +76,18 @@
<MudSelect Class="mt-3" Label="Music Video Credits Mode" @bind-Value="_model.MusicVideoCreditsMode" For="@(() => _model.MusicVideoCreditsMode)">
<MudSelectItem Value="@(ChannelMusicVideoCreditsMode.None)">None</MudSelectItem>
<MudSelectItem Value="@(ChannelMusicVideoCreditsMode.GenerateSubtitles)">Generate Subtitles</MudSelectItem>
<MudSelectItem Value="@(ChannelMusicVideoCreditsMode.TemplateSubtitles)">Template Subtitles</MudSelectItem>
</MudSelect>
<MudSelect Class="mt-3"
Label="Music Video Credits Template"
@bind-Value="_model.MusicVideoCreditsTemplate"
For="@(() => _model.MusicVideoCreditsTemplate)"
Disabled="@(_model.MusicVideoCreditsMode != ChannelMusicVideoCreditsMode.TemplateSubtitles)">
<MudSelectItem T="string" Value="@((string)null)">(none)</MudSelectItem>
@foreach (string template in _musicVideoCreditsTemplates)
{
<MudSelectItem T="string" Value="@template">@template</MudSelectItem>
}
</MudSelect>
<MudGrid Class="mt-3" Style="align-items: center" Justify="Justify.Center">
<MudItem xs="6">
@ -139,6 +152,7 @@ @@ -139,6 +152,7 @@
private List<CultureInfo> _availableCultures;
private List<WatermarkViewModel> _watermarks;
private List<FillerPresetViewModel> _fillerPresets;
private List<string> _musicVideoCreditsTemplates;
public void Dispose()
{
@ -152,6 +166,7 @@ @@ -152,6 +166,7 @@
_availableCultures = await Mediator.Send(new GetAllLanguageCodes(), _cts.Token);
await LoadWatermarks(_cts.Token);
await LoadFillerPresets(_cts.Token);
await LoadMusicVideoCreditsTemplates(_cts.Token);
if (Id.HasValue)
{
@ -174,6 +189,7 @@ @@ -174,6 +189,7 @@
_model.PreferredSubtitleLanguageCode = channelViewModel.PreferredSubtitleLanguageCode;
_model.SubtitleMode = channelViewModel.SubtitleMode;
_model.MusicVideoCreditsMode = channelViewModel.MusicVideoCreditsMode;
_model.MusicVideoCreditsTemplate = channelViewModel.MusicVideoCreditsTemplate;
},
() => NavigationManager.NavigateTo("404"));
}
@ -211,6 +227,9 @@ @@ -211,6 +227,9 @@
_fillerPresets = await Mediator.Send(new GetAllFillerPresets(), cancellationToken)
.Map(list => list.Filter(vm => vm.FillerKind == FillerKind.Fallback).ToList());
private async Task LoadMusicVideoCreditsTemplates(CancellationToken cancellationToken) =>
_musicVideoCreditsTemplates = await Mediator.Send(new GetMusicVideoCreditTemplates(), cancellationToken);
private async Task HandleSubmitAsync()
{
_messageStore.Clear();

13
ErsatzTV/Resources/Templates/_default.ass.sbntxt

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
[Script Info]
ScriptType: v4.00+
WrapStyle: 0
ScaledBorderAndShadow: yes
YCbCr Matrix: None
PlayResX: {{ resolution.width }}
PlayResY: {{ resolution.height }}
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, OutlineColour, BorderStyle, Outline, Shadow, Alignment, Encoding
Style: Default,OPTIKabel-Heavy,{{ resolution.height // 20.0 }},&HFFFFFF,&H444444,1,1,3,0,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,00:00:09.00,00:00:16.00,Default,,{{ resolution.width * 0.03 | math.round }},{{ resolution.width * 0.03 | math.round }},{{ resolution.height * 0.05 | math.round }},,{fad(1200, 1200)}{{ if all_artists | array.size == 0 }}{{ artist }}{{ else }}{{ all_artists | array.join " / " }}{{ end }}\N"{{ title }}"{{ if album }}\N{{ album }}{{ end }}

30
ErsatzTV/Services/RunOnce/ResourceExtractorService.cs

@ -22,6 +22,12 @@ public class ResourceExtractorService : IHostedService @@ -22,6 +22,12 @@ public class ResourceExtractorService : IHostedService
await ExtractFontResource(assembly, "Roboto-Regular.ttf", cancellationToken);
await ExtractFontResource(assembly, "OPTIKabel-Heavy.otf", cancellationToken);
await ExtractTemplateResource(
assembly,
"_default.ass.sbntxt",
FileSystemLayout.MusicVideoCreditsTemplatesFolder,
cancellationToken);
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
@ -31,8 +37,7 @@ public class ResourceExtractorService : IHostedService @@ -31,8 +37,7 @@ public class ResourceExtractorService : IHostedService
await using Stream resource = assembly.GetManifestResourceStream($"ErsatzTV.Resources.{name}");
if (resource != null)
{
await using FileStream fs = File.Create(
Path.Combine(FileSystemLayout.ResourcesCacheFolder, name));
await using FileStream fs = File.Create(Path.Combine(FileSystemLayout.ResourcesCacheFolder, name));
await resource.CopyToAsync(fs, cancellationToken);
}
}
@ -42,15 +47,28 @@ public class ResourceExtractorService : IHostedService @@ -42,15 +47,28 @@ public class ResourceExtractorService : IHostedService
await using Stream resource = assembly.GetManifestResourceStream($"ErsatzTV.Resources.{name}");
if (resource != null)
{
await using FileStream fs = File.Create(
Path.Combine(FileSystemLayout.ResourcesCacheFolder, name));
await using FileStream fs = File.Create(Path.Combine(FileSystemLayout.ResourcesCacheFolder, name));
await resource.CopyToAsync(fs, cancellationToken);
resource.Position = 0;
await using FileStream fontCacheFileStream = File.Create(
Path.Combine(FileSystemLayout.FontsCacheFolder, name));
await using FileStream fontCacheFileStream =
File.Create(Path.Combine(FileSystemLayout.FontsCacheFolder, name));
await resource.CopyToAsync(fontCacheFileStream, cancellationToken);
}
}
private static async Task ExtractTemplateResource(
Assembly assembly,
string name,
string targetFolder,
CancellationToken cancellationToken)
{
await using Stream resource = assembly.GetManifestResourceStream($"ErsatzTV.Resources.Templates.{name}");
if (resource != null)
{
await using FileStream fs = File.Create(Path.Combine(targetFolder, name));
await resource.CopyToAsync(fs, cancellationToken);
}
}
}

11
ErsatzTV/Startup.cs

@ -39,6 +39,7 @@ using ErsatzTV.Infrastructure.Data; @@ -39,6 +39,7 @@ using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Data.Repositories;
using ErsatzTV.Infrastructure.Data.Repositories.Caching;
using ErsatzTV.Infrastructure.Emby;
using ErsatzTV.Infrastructure.FFmpeg;
using ErsatzTV.Infrastructure.GitHub;
using ErsatzTV.Infrastructure.Health;
using ErsatzTV.Infrastructure.Health.Checks;
@ -188,6 +189,16 @@ public class Startup @@ -188,6 +189,16 @@ public class Startup
Directory.CreateDirectory(FileSystemLayout.FontsCacheFolder);
}
if (!Directory.Exists(FileSystemLayout.TemplatesFolder))
{
Directory.CreateDirectory(FileSystemLayout.TemplatesFolder);
}
if (!Directory.Exists(FileSystemLayout.MusicVideoCreditsTemplatesFolder))
{
Directory.CreateDirectory(FileSystemLayout.MusicVideoCreditsTemplatesFolder);
}
Log.Logger.Information("Database is at {DatabasePath}", FileSystemLayout.DatabasePath);
// until we add a setting for a file-specific scheme://host:port to access

15
ErsatzTV/ViewModels/ChannelEditViewModel.cs

@ -5,6 +5,7 @@ namespace ErsatzTV.ViewModels; @@ -5,6 +5,7 @@ namespace ErsatzTV.ViewModels;
public class ChannelEditViewModel
{
private string _musicVideoCreditsTemplate;
public int Id { get; set; }
public string Name { get; set; }
public string Group { get; set; }
@ -21,6 +22,14 @@ public class ChannelEditViewModel @@ -21,6 +22,14 @@ public class ChannelEditViewModel
public ChannelSubtitleMode SubtitleMode { get; set; }
public ChannelMusicVideoCreditsMode MusicVideoCreditsMode { get; set; }
public string MusicVideoCreditsTemplate
{
get => MusicVideoCreditsMode == ChannelMusicVideoCreditsMode.TemplateSubtitles
? _musicVideoCreditsTemplate
: null;
set => _musicVideoCreditsTemplate = value;
}
public UpdateChannel ToUpdate() =>
new(
Id,
@ -37,7 +46,8 @@ public class ChannelEditViewModel @@ -37,7 +46,8 @@ public class ChannelEditViewModel
FallbackFillerId,
PreferredSubtitleLanguageCode,
SubtitleMode,
MusicVideoCreditsMode);
MusicVideoCreditsMode,
MusicVideoCreditsTemplate);
public CreateChannel ToCreate() =>
new(
@ -54,5 +64,6 @@ public class ChannelEditViewModel @@ -54,5 +64,6 @@ public class ChannelEditViewModel
FallbackFillerId,
PreferredSubtitleLanguageCode,
SubtitleMode,
MusicVideoCreditsMode);
MusicVideoCreditsMode,
MusicVideoCreditsTemplate);
}

Loading…
Cancel
Save