diff --git a/ErsatzTV/ErsatzTV.csproj b/ErsatzTV/ErsatzTV.csproj
index 514d33ec8..bcc13489b 100644
--- a/ErsatzTV/ErsatzTV.csproj
+++ b/ErsatzTV/ErsatzTV.csproj
@@ -98,6 +98,8 @@
+
+
@@ -105,10 +107,10 @@
-
-
+
+
diff --git a/ErsatzTV/Resources/OPTIKabel-Heavy.otf b/ErsatzTV/Resources/Fonts/OPTIKabel-Heavy.otf
similarity index 100%
rename from ErsatzTV/Resources/OPTIKabel-Heavy.otf
rename to ErsatzTV/Resources/Fonts/OPTIKabel-Heavy.otf
diff --git a/ErsatzTV/Resources/Roboto-Regular.ttf b/ErsatzTV/Resources/Fonts/Roboto-Regular.ttf
similarity index 100%
rename from ErsatzTV/Resources/Roboto-Regular.ttf
rename to ErsatzTV/Resources/Fonts/Roboto-Regular.ttf
diff --git a/ErsatzTV/Resources/Templates/_ArtistTitleAlbum_CenterTop.sbntxt b/ErsatzTV/Resources/Templates/_ArtistTitleAlbum_CenterTop.sbntxt
new file mode 100644
index 000000000..de29ab7a1
--- /dev/null
+++ b/ErsatzTV/Resources/Templates/_ArtistTitleAlbum_CenterTop.sbntxt
@@ -0,0 +1,46 @@
+[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, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
+Style: RoboR,Roboto-Regular.ttf,{{ resolution.height // 20.0 }},&HFFFFFF,&HFFFFFF,&H444444,&H00000000,0,0,0,0,70,100,0,0,1,1,3,8,10,10,10,1
+
+[Events]
+{{~ _margin3_p = resolution.width * 0.03 | math.round ~}}
+{{~ _margin5_p = resolution.height * 0.05 | math.round ~}}
+{{~ _seek_s = stream_seek.total_seconds | math.round ~}}
+{{~ _duration_s = duration.total_seconds | math.round ~}}
+{{~ _min_interval_s = 60 ~}}
+{{~ if (_duration_s | math.minus _seek_s) > _min_interval_s ~}}
+{{~ _first_start_ts = timespan.from_seconds (_seek_s | math.plus 9) ~}}
+{{~ _first_end_ts = timespan.from_seconds (_seek_s | math.plus 16) ~}}
+{{~ end ~}}
+{{~ _last_start_ts = timespan.from_seconds (_duration_s | math.minus 16) ~}}
+{{~ _last_end_ts = timespan.from_seconds (_duration_s | math.minus 9) ~}}
+{{~ if all_artists | array.size == 0 ~}}
+{{~ capture _artists_t ~}}{{~ artist ~}}{{~ end ~}}
+{{ else }}
+{{~ capture _artists_t ~}}{{~ all_artists | array.join " | " ~}}{{~ end ~}}
+{{~ end ~}}
+{{~ capture _fancy_title_t ~}}{%{{\b1}{\fscy150}{\1c&HC2C1BF&}}%}{{~ title ~}}{{~ end ~}}
+Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
+Dialogue: 0,{{ _first_start_ts }}.00,{{ _first_end_ts }}.00,RoboR,,{{ _margin3_p }},{{ _margin3_p }},{{ _margin5_p }},,{\fad(1200, 1200)}{{ _artists_t }}
+Dialogue: 0,{{ _first_start_ts }}.00,{{ _first_end_ts }}.00,RoboR,,{{ _margin3_p }},{{ _margin3_p }},{{ _margin5_p }},,{\fad(1200, 1200)}{{ _fancy_title_t }}
+Dialogue: 0,{{ _first_start_ts }}.00,{{ _first_end_ts }}.00,RoboR,,{{ _margin3_p }},{{ _margin3_p }},{{ _margin5_p }},,{\fad(1200, 1200)}{{ album }}
+{{~ _interval_s = 300 ~}}
+{{~ _display_time_s = 7 ~}}
+{{~ i = (_seek_s | math.plus _interval_s) ~}}
+{{~ while i < (_duration_s | math.minus _min_interval_s) ~}}
+{{~ _start_ts = timespan.from_seconds i ~}}
+{{~ _end_ts = timespan.from_seconds (i | math.plus _display_time_s) ~}}
+Dialogue: 0,{{ _start_ts }}.00,{{ _end_ts }}.00,RoboR,,{{ _margin3_p }},{{ _margin3_p }},{{ _margin5_p }},,{\fad(1200, 1200)}{{ _artists_t }}\N{{ title }}\N{{ album }}
+{{~ i += _interval_s ~}}
+{{~ end ~}}
+Dialogue: 0,{{ _last_start_ts }}.00,{{ _last_end_ts }}.00,RoboR,,{{ _margin3_p }},{{ _margin3_p }},{{ _margin5_p }},,{\fad(1200, 1200)}{{ _artists_t }}
+Dialogue: 0,{{ _last_start_ts }}.00,{{ _last_end_ts }}.00,RoboR,,{{ _margin3_p }},{{ _margin3_p }},{{ _margin5_p }},,{\fad(1200, 1200)}{{ _fancy_title_t }}
+Dialogue: 0,{{ _last_start_ts }}.00,{{ _last_end_ts }}.00,RoboR,,{{ _margin3_p }},{{ _margin3_p }},{{ _margin5_p }},,{\fad(1200, 1200)}{{ album }}
\ No newline at end of file
diff --git a/ErsatzTV/Resources/Templates/_ArtistTitle_LeftMiddle.sbntxt b/ErsatzTV/Resources/Templates/_ArtistTitle_LeftMiddle.sbntxt
new file mode 100644
index 000000000..1142b1756
--- /dev/null
+++ b/ErsatzTV/Resources/Templates/_ArtistTitle_LeftMiddle.sbntxt
@@ -0,0 +1,44 @@
+[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, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
+Style: RoboR,Roboto-Regular.ttf,{{ resolution.height // 20.0 }},&HFFFFFF,&HFFFFFF,&H444444,&H00000000,0,0,0,0,70,100,0,0,1,1,3,4,10,10,10,1
+
+[Events]
+{{~ _margin3_p = resolution.width * 0.03 | math.round ~}}
+{{~ _margin5_p = resolution.height * 0.05 | math.round ~}}
+{{~ _seek_s = stream_seek.total_seconds | math.round ~}}
+{{~ _duration_s = duration.total_seconds | math.round ~}}
+{{~ _min_interval_s = 60 ~}}
+{{~ if (_duration_s | math.minus _seek_s) > _min_interval_s ~}}
+{{~ _first_start_ts = timespan.from_seconds (_seek_s | math.plus 9) ~}}
+{{~ _first_end_ts = timespan.from_seconds (_seek_s | math.plus 16) ~}}
+{{~ end ~}}
+{{~ _last_start_ts = timespan.from_seconds (_duration_s | math.minus 16) ~}}
+{{~ _last_end_ts = timespan.from_seconds (_duration_s | math.minus 9) ~}}
+{{~ if all_artists | array.size == 0 ~}}
+{{~ capture _artists_t ~}}{{~ artist ~}}{{~ end ~}}
+{{~ else ~}}
+{{~ capture _artists_t ~}}{{~ all_artists | array.join " | " ~}}{{~ end ~}}
+{{~ end ~}}
+{{~ capture _fancy_title_t ~}}{%{{\b1}{\fscy150}{\1c&HC2C1BF&}}%}{{~ title ~}}{{~ end ~}}
+Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
+Dialogue: 0,{{ _first_start_ts }}.00,{{ _first_end_ts }}.00,RoboR,,{{ _margin3_p }},{{ _margin3_p }},{{ _margin5_p }},,{\fad(1800, 1200)}{{ _fancy_title_t }}
+Dialogue: 0,{{ _first_start_ts }}.00,{{ _first_end_ts }}.00,RoboR,,{{ _margin3_p }},{{ _margin3_p }},{{ _margin5_p }},,{\fad(1200, 1800)}{{ _artists_t }}
+{{~ _interval_s = 300 ~}}
+{{~ _display_time_s = 7 ~}}
+{{~ i = (_seek_s | math.plus _interval_s) ~}}
+{{~ while i < (_duration_s | math.minus _min_interval_s) ~}}
+{{~ _start_ts = timespan.from_seconds i ~}}
+{{~ _end_ts = timespan.from_seconds (i | math.plus _display_time_s) ~}}
+Dialogue: 0,{{ _start_ts }}.00,{{ _end_ts }}.00,RoboR,,{{ _margin3_p }},{{ _margin3_p }},{{ _margin5_p }},,{\fad(1200, 1200)}{{ _artists_t }}\N{{ title }}
+{{~ i += _interval_s ~}}
+{{~ end ~}}
+Dialogue: 0,{{ _last_start_ts }}.00,{{ _last_end_ts }}.00,RoboR,,{{ _margin3_p }},{{ _margin3_p }},{{ _margin5_p }},,{\fad(1800, 1200)}{{ _fancy_title_t }}
+Dialogue: 0,{{ _last_start_ts }}.00,{{ _last_end_ts }}.00,RoboR,,{{ _margin3_p }},{{ _margin3_p }},{{ _margin5_p }},,{\fad(1200, 1800)}{{ _artists_t }}
\ No newline at end of file
diff --git a/ErsatzTV/Services/RunOnce/ResourceExtractorService.cs b/ErsatzTV/Services/RunOnce/ResourceExtractorService.cs
index 193b7ec93..6e5cd17a0 100644
--- a/ErsatzTV/Services/RunOnce/ResourceExtractorService.cs
+++ b/ErsatzTV/Services/RunOnce/ResourceExtractorService.cs
@@ -29,6 +29,18 @@ public class ResourceExtractorService : IHostedService
FileSystemLayout.MusicVideoCreditsTemplatesFolder,
cancellationToken);
+ await ExtractTemplateResource(
+ assembly,
+ "_ArtistTitle_LeftMiddle.sbntxt",
+ FileSystemLayout.MusicVideoCreditsTemplatesFolder,
+ cancellationToken);
+
+ await ExtractTemplateResource(
+ assembly,
+ "_ArtistTitleAlbum_CenterTop.sbntxt",
+ FileSystemLayout.MusicVideoCreditsTemplatesFolder,
+ cancellationToken);
+
await ExtractScriptResource(
assembly,
"_threePartEpisodes.js",
@@ -62,7 +74,7 @@ public class ResourceExtractorService : IHostedService
private static async Task ExtractFontResource(Assembly assembly, string name, CancellationToken cancellationToken)
{
- await using Stream resource = assembly.GetManifestResourceStream($"ErsatzTV.Resources.{name}");
+ await using Stream resource = assembly.GetManifestResourceStream($"ErsatzTV.Resources.Fonts.{name}");
if (resource != null)
{
await using FileStream fs = File.Create(Path.Combine(FileSystemLayout.ResourcesCacheFolder, name));