Browse Source

allow custom song background images (#2632)

* allow custom song background images

* allow custom missing album art
pull/2633/head
Jason Dove 2 months ago committed by GitHub
parent
commit
1e0bba0dc6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 50
      ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs
  3. 8
      ErsatzTV.Core.Tests/Fakes/FakeLocalFileSystem.cs
  4. 11
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  5. 14
      ErsatzTV.Core/FFmpeg/SongVideoGenerator.cs
  6. 1
      ErsatzTV.Core/Interfaces/Metadata/ILocalFileSystem.cs
  7. 6
      ErsatzTV.Core/Metadata/LocalFileSystem.cs
  8. 4
      ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs
  9. 8
      ErsatzTV.Scanner.Tests/Core/Fakes/FakeLocalFileSystem.cs
  10. 8
      ErsatzTV/ErsatzTV.csproj
  11. 0
      ErsatzTV/Resources/_song_album_cover_512.png
  12. 0
      ErsatzTV/Resources/_song_background_1.png
  13. 0
      ErsatzTV/Resources/_song_background_2.png
  14. 0
      ErsatzTV/Resources/_song_background_3.png
  15. 8
      ErsatzTV/Services/RunOnce/ResourceExtractorService.cs

2
CHANGELOG.md

@ -41,6 +41,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -41,6 +41,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Error streams will prioritize using `background.png` if it exists
- Replacing this `background.png` file will allow custom error/offline backgrounds
- Add `Troubleshoot Playback` buttons on movie and episode detail pages
- Add song background and missing album art customization
- Default files start with an underscore; custom versions must remove the underscore
### Fixed
- Fix HLS Direct playback with Jellyfin 10.11

50
ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs

@ -881,13 +881,9 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -881,13 +881,9 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
private string GetMovieTemplateFileName()
{
string templateFileName = Path.Combine(FileSystemLayout.ChannelGuideTemplatesFolder, "movie.sbntxt");
// fall back to default template
if (!_localFileSystem.FileExists(templateFileName))
{
templateFileName = Path.Combine(FileSystemLayout.ChannelGuideTemplatesFolder, "_movie.sbntxt");
}
string templateFileName = _localFileSystem.GetCustomOrDefaultFile(
FileSystemLayout.ChannelGuideTemplatesFolder,
"movie.sbntxt");
// fail if file doesn't exist
if (!_localFileSystem.FileExists(templateFileName))
@ -904,13 +900,9 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -904,13 +900,9 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
private string GetEpisodeTemplateFileName()
{
string templateFileName = Path.Combine(FileSystemLayout.ChannelGuideTemplatesFolder, "episode.sbntxt");
// fall back to default template
if (!_localFileSystem.FileExists(templateFileName))
{
templateFileName = Path.Combine(FileSystemLayout.ChannelGuideTemplatesFolder, "_episode.sbntxt");
}
string templateFileName = _localFileSystem.GetCustomOrDefaultFile(
FileSystemLayout.ChannelGuideTemplatesFolder,
"episode.sbntxt");
// fail if file doesn't exist
if (!_localFileSystem.FileExists(templateFileName))
@ -927,13 +919,9 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -927,13 +919,9 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
private string GetMusicVideoTemplateFileName()
{
string templateFileName = Path.Combine(FileSystemLayout.ChannelGuideTemplatesFolder, "musicVideo.sbntxt");
// fall back to default template
if (!_localFileSystem.FileExists(templateFileName))
{
templateFileName = Path.Combine(FileSystemLayout.ChannelGuideTemplatesFolder, "_musicVideo.sbntxt");
}
string templateFileName = _localFileSystem.GetCustomOrDefaultFile(
FileSystemLayout.ChannelGuideTemplatesFolder,
"musicVideo.sbntxt");
// fail if file doesn't exist
if (!_localFileSystem.FileExists(templateFileName))
@ -950,13 +938,9 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -950,13 +938,9 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
private string GetSongTemplateFileName()
{
string templateFileName = Path.Combine(FileSystemLayout.ChannelGuideTemplatesFolder, "song.sbntxt");
// fall back to default template
if (!_localFileSystem.FileExists(templateFileName))
{
templateFileName = Path.Combine(FileSystemLayout.ChannelGuideTemplatesFolder, "_song.sbntxt");
}
string templateFileName = _localFileSystem.GetCustomOrDefaultFile(
FileSystemLayout.ChannelGuideTemplatesFolder,
"song.sbntxt");
// fail if file doesn't exist
if (!_localFileSystem.FileExists(templateFileName))
@ -973,13 +957,9 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -973,13 +957,9 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
private string GetOtherVideoTemplateFileName()
{
string templateFileName = Path.Combine(FileSystemLayout.ChannelGuideTemplatesFolder, "otherVideo.sbntxt");
// fall back to default template
if (!_localFileSystem.FileExists(templateFileName))
{
templateFileName = Path.Combine(FileSystemLayout.ChannelGuideTemplatesFolder, "_otherVideo.sbntxt");
}
string templateFileName = _localFileSystem.GetCustomOrDefaultFile(
FileSystemLayout.ChannelGuideTemplatesFolder,
"otherVideo.sbntxt");
// fail if file doesn't exist
if (!_localFileSystem.FileExists(templateFileName))

8
ErsatzTV.Core.Tests/Fakes/FakeLocalFileSystem.cs

@ -5,8 +5,6 @@ namespace ErsatzTV.Core.Tests.Fakes; @@ -5,8 +5,6 @@ namespace ErsatzTV.Core.Tests.Fakes;
public class FakeLocalFileSystem : ILocalFileSystem
{
public static readonly byte[] TestBytes = { 1, 2, 3, 4, 5 };
private readonly List<FakeFileEntry> _files;
private readonly List<FakeFolderEntry> _folders;
@ -75,7 +73,11 @@ public class FakeLocalFileSystem : ILocalFileSystem @@ -75,7 +73,11 @@ public class FakeLocalFileSystem : ILocalFileSystem
public Task<byte[]> GetHash(string path) => throw new NotSupportedException();
public Task<byte[]> ReadAllBytes(string path) => TestBytes.AsTask();
public string GetCustomOrDefaultFile(string folder, string file)
{
string path = Path.Combine(folder, file);
return FileExists(path) ? path : Path.Combine(folder, $"_{file}");
}
private static List<DirectoryInfo> Split(DirectoryInfo path)
{

11
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -30,6 +30,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -30,6 +30,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
private readonly IMpegTsScriptService _mpegTsScriptService;
private readonly ILocalStatisticsProvider _localStatisticsProvider;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly ILocalFileSystem _localFileSystem;
private readonly ICustomStreamSelector _customStreamSelector;
private readonly FFmpegProcessService _ffmpegProcessService;
private readonly IFFmpegStreamSelector _ffmpegStreamSelector;
@ -49,6 +50,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -49,6 +50,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
IMpegTsScriptService mpegTsScriptService,
ILocalStatisticsProvider localStatisticsProvider,
IMediaItemRepository mediaItemRepository,
ILocalFileSystem localFileSystem,
ILogger<FFmpegLibraryProcessService> logger)
{
_ffmpegProcessService = ffmpegProcessService;
@ -62,6 +64,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -62,6 +64,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
_mpegTsScriptService = mpegTsScriptService;
_localStatisticsProvider = localStatisticsProvider;
_mediaItemRepository = mediaItemRepository;
_localFileSystem = localFileSystem;
_logger = logger;
}
@ -759,11 +762,9 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -759,11 +762,9 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
}
}
string videoPath = Path.Combine(FileSystemLayout.ResourcesCacheFolder, "background.png");
if (!File.Exists(videoPath))
{
videoPath = Path.Combine(FileSystemLayout.ResourcesCacheFolder, "_background.png");
}
string videoPath = _localFileSystem.GetCustomOrDefaultFile(
FileSystemLayout.ResourcesCacheFolder,
"background.png");
var videoVersion = BackgroundImageMediaVersion.ForPath(videoPath, desiredResolution);

14
ErsatzTV.Core/FFmpeg/SongVideoGenerator.cs

@ -3,6 +3,7 @@ using System.Text; @@ -3,6 +3,7 @@ using System.Text;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.FFmpeg;
using ErsatzTV.Core.Interfaces.Images;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.FFmpeg.State;
namespace ErsatzTV.Core.FFmpeg;
@ -12,6 +13,7 @@ public class SongVideoGenerator : ISongVideoGenerator @@ -12,6 +13,7 @@ public class SongVideoGenerator : ISongVideoGenerator
private static readonly Random Random = new();
private static readonly Lock RandomLock = new();
private readonly IFFmpegProcessService _ffmpegProcessService;
private readonly ILocalFileSystem _localFileSystem;
private readonly IImageCache _imageCache;
private readonly ITempFilePool _tempFilePool;
@ -19,11 +21,13 @@ public class SongVideoGenerator : ISongVideoGenerator @@ -19,11 +21,13 @@ public class SongVideoGenerator : ISongVideoGenerator
public SongVideoGenerator(
ITempFilePool tempFilePool,
IImageCache imageCache,
IFFmpegProcessService ffmpegProcessService)
IFFmpegProcessService ffmpegProcessService,
ILocalFileSystem localFileSystem)
{
_tempFilePool = tempFilePool;
_imageCache = imageCache;
_ffmpegProcessService = ffmpegProcessService;
_localFileSystem = localFileSystem;
}
public async Task<Tuple<string, MediaVersion>> GenerateSongVideo(
@ -46,14 +50,14 @@ public class SongVideoGenerator : ISongVideoGenerator @@ -46,14 +50,14 @@ public class SongVideoGenerator : ISongVideoGenerator
};
string[] backgrounds =
{
[
"song_background_1.png",
"song_background_2.png",
"song_background_3.png"
};
];
// use random ETV color by default
string backgroundPath = Path.Combine(
string backgroundPath = _localFileSystem.GetCustomOrDefaultFile(
FileSystemLayout.ResourcesCacheFolder,
backgrounds[NextRandom(backgrounds.Length)]);
@ -163,7 +167,7 @@ public class SongVideoGenerator : ISongVideoGenerator @@ -163,7 +167,7 @@ public class SongVideoGenerator : ISongVideoGenerator
{
Id = 0,
ArtworkKind = ArtworkKind.Thumbnail,
Path = Path.Combine(FileSystemLayout.ResourcesCacheFolder, "song_album_cover_512.png")
Path = _localFileSystem.GetCustomOrDefaultFile(FileSystemLayout.ResourcesCacheFolder, "song_album_cover_512.png")
});
// signal that we want to use cover art as watermark

1
ErsatzTV.Core/Interfaces/Metadata/ILocalFileSystem.cs

@ -18,4 +18,5 @@ public interface ILocalFileSystem @@ -18,4 +18,5 @@ public interface ILocalFileSystem
Task<string> ReadAllText(string path);
Task<string[]> ReadAllLines(string path);
Task<byte[]> GetHash(string path);
string GetCustomOrDefaultFile(string folder, string file);
}

6
ErsatzTV.Core/Metadata/LocalFileSystem.cs

@ -194,4 +194,10 @@ public class LocalFileSystem(IClient client, ILogger<LocalFileSystem> logger) : @@ -194,4 +194,10 @@ public class LocalFileSystem(IClient client, ILogger<LocalFileSystem> logger) :
await using var stream = File.OpenRead(path);
return await md5.ComputeHashAsync(stream);
}
public string GetCustomOrDefaultFile(string folder, string file)
{
string path = Path.Combine(folder, file);
return FileExists(path) ? path : Path.Combine(folder, $"_{file}");
}
}

4
ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs

@ -285,9 +285,10 @@ public class TranscodingTests @@ -285,9 +285,10 @@ public class TranscodingTests
Substitute.For<IMpegTsScriptService>(),
Substitute.For<ILocalStatisticsProvider>(),
Substitute.For<IMediaItemRepository>(),
localFileSystem,
LoggerFactory.CreateLogger<FFmpegLibraryProcessService>());
var songVideoGenerator = new SongVideoGenerator(tempFilePool, mockImageCache, service);
var songVideoGenerator = new SongVideoGenerator(tempFilePool, mockImageCache, service, localFileSystem);
var channel = new Channel(Guid.NewGuid())
{
@ -994,6 +995,7 @@ public class TranscodingTests @@ -994,6 +995,7 @@ public class TranscodingTests
Substitute.For<IMpegTsScriptService>(),
Substitute.For<ILocalStatisticsProvider>(),
Substitute.For<IMediaItemRepository>(),
Substitute.For<ILocalFileSystem>(),
LoggerFactory.CreateLogger<FFmpegLibraryProcessService>());
return service;

8
ErsatzTV.Scanner.Tests/Core/Fakes/FakeLocalFileSystem.cs

@ -6,8 +6,6 @@ namespace ErsatzTV.Scanner.Tests.Core.Fakes; @@ -6,8 +6,6 @@ namespace ErsatzTV.Scanner.Tests.Core.Fakes;
public class FakeLocalFileSystem : ILocalFileSystem
{
public static readonly byte[] TestBytes = { 1, 2, 3, 4, 5 };
private readonly List<FakeFileEntry> _files;
private readonly List<FakeFolderEntry> _folders;
@ -64,7 +62,11 @@ public class FakeLocalFileSystem : ILocalFileSystem @@ -64,7 +62,11 @@ public class FakeLocalFileSystem : ILocalFileSystem
public Task<string[]> ReadAllLines(string path) => throw new NotImplementedException();
public Task<byte[]> GetHash(string path) => throw new NotImplementedException();
public Task<byte[]> ReadAllBytes(string path) => TestBytes.AsTask();
public string GetCustomOrDefaultFile(string folder, string file)
{
string path = Path.Combine(folder, file);
return FileExists(path) ? path : Path.Combine(folder, $"_{file}");
}
private static List<DirectoryInfo> Split(DirectoryInfo path)
{

8
ErsatzTV/ErsatzTV.csproj

@ -85,10 +85,10 @@ @@ -85,10 +85,10 @@
<EmbeddedResource Include="Resources\Scripts\MpegTs\run.sh" />
<EmbeddedResource Include="Resources\Scripts\MpegTs\run.bat" />
<EmbeddedResource Include="Resources\Scripts\MpegTs\mpegts.yml" />
<EmbeddedResource Include="Resources\song_album_cover_512.png" />
<EmbeddedResource Include="Resources\song_background_1.png" />
<EmbeddedResource Include="Resources\song_background_2.png" />
<EmbeddedResource Include="Resources\song_background_3.png" />
<EmbeddedResource Include="Resources\_song_album_cover_512.png" />
<EmbeddedResource Include="Resources\_song_background_1.png" />
<EmbeddedResource Include="Resources\_song_background_2.png" />
<EmbeddedResource Include="Resources\_song_background_3.png" />
<EmbeddedResource Include="Resources\song_progress_overlay.png" />
<EmbeddedResource Include="Resources\song_progress_overlay_43.png" />
<EmbeddedResource Include="Resources\ErsatzTV.png" />

0
ErsatzTV/Resources/song_album_cover_512.png → ErsatzTV/Resources/_song_album_cover_512.png

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 167 KiB

0
ErsatzTV/Resources/song_background_1.png → ErsatzTV/Resources/_song_background_1.png

Before

Width:  |  Height:  |  Size: 257 KiB

After

Width:  |  Height:  |  Size: 257 KiB

0
ErsatzTV/Resources/song_background_2.png → ErsatzTV/Resources/_song_background_2.png

Before

Width:  |  Height:  |  Size: 327 KiB

After

Width:  |  Height:  |  Size: 327 KiB

0
ErsatzTV/Resources/song_background_3.png → ErsatzTV/Resources/_song_background_3.png

Before

Width:  |  Height:  |  Size: 332 KiB

After

Width:  |  Height:  |  Size: 332 KiB

8
ErsatzTV/Services/RunOnce/ResourceExtractorService.cs

@ -17,10 +17,10 @@ public class ResourceExtractorService : BackgroundService @@ -17,10 +17,10 @@ public class ResourceExtractorService : BackgroundService
Assembly assembly = typeof(ResourceExtractorService).GetTypeInfo().Assembly;
await ExtractResource(assembly, "_background.png", stoppingToken);
await ExtractResource(assembly, "song_album_cover_512.png", stoppingToken);
await ExtractResource(assembly, "song_background_1.png", stoppingToken);
await ExtractResource(assembly, "song_background_2.png", stoppingToken);
await ExtractResource(assembly, "song_background_3.png", stoppingToken);
await ExtractResource(assembly, "_song_album_cover_512.png", stoppingToken);
await ExtractResource(assembly, "_song_background_1.png", stoppingToken);
await ExtractResource(assembly, "_song_background_2.png", stoppingToken);
await ExtractResource(assembly, "_song_background_3.png", stoppingToken);
await ExtractResource(assembly, "song_progress_overlay.png", stoppingToken);
await ExtractResource(assembly, "song_progress_overlay_43.png", stoppingToken);
await ExtractResource(assembly, "ErsatzTV.png", stoppingToken);

Loading…
Cancel
Save