diff --git a/ErsatzTV.Application/FFmpeg/Commands/RefreshFFmpegCapabilitiesHandler.cs b/ErsatzTV.Application/FFmpeg/Commands/RefreshFFmpegCapabilitiesHandler.cs index 960851ced..7f644ae06 100644 --- a/ErsatzTV.Application/FFmpeg/Commands/RefreshFFmpegCapabilitiesHandler.cs +++ b/ErsatzTV.Application/FFmpeg/Commands/RefreshFFmpegCapabilitiesHandler.cs @@ -1,4 +1,6 @@ +using ErsatzTV.Core; using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.FFmpeg.Capabilities; using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Extensions; @@ -8,7 +10,8 @@ namespace ErsatzTV.Application.FFmpeg; public class RefreshFFmpegCapabilitiesHandler( IDbContextFactory dbContextFactory, - IHardwareCapabilitiesFactory hardwareCapabilitiesFactory) + IHardwareCapabilitiesFactory hardwareCapabilitiesFactory, + ILocalStatisticsProvider localStatisticsProvider) : IRequestHandler { public async Task Handle(RefreshFFmpegCapabilities request, CancellationToken cancellationToken) @@ -24,6 +27,19 @@ public class RefreshFFmpegCapabilitiesHandler( foreach (string ffmpegPath in maybeFFmpegPath) { _ = await hardwareCapabilitiesFactory.GetFFmpegCapabilities(ffmpegPath); + + Option maybeFFprobePath = await dbContext.ConfigElements + .GetValue(ConfigElementKey.FFprobePath, cancellationToken) + .FilterT(File.Exists); + + foreach (string ffprobePath in maybeFFprobePath) + { + Either result = await localStatisticsProvider.GetStatistics( + ffprobePath, + Path.Combine(FileSystemLayout.ResourcesCacheFolder, "test.avs")); + + hardwareCapabilitiesFactory.SetAviSynthInstalled(result.IsRight); + } } } } diff --git a/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs b/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs index 0b2d031b1..e49eac0bc 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using System.Globalization; using System.Threading.Channels; +using ErsatzTV.Application.FFmpeg; using ErsatzTV.Application.Subtitles; using ErsatzTV.Core; using ErsatzTV.Core.Domain; @@ -151,6 +152,8 @@ public class UpdateFFmpegSettingsHandler( request.Settings.InitialSegmentCount, cancellationToken); + await workerChannel.WriteAsync(new RefreshFFmpegCapabilities(), cancellationToken); + return Unit.Default; } } diff --git a/ErsatzTV.Application/Troubleshooting/Queries/GetTroubleshootingInfoHandler.cs b/ErsatzTV.Application/Troubleshooting/Queries/GetTroubleshootingInfoHandler.cs index 958aeba83..63a93a451 100644 --- a/ErsatzTV.Application/Troubleshooting/Queries/GetTroubleshootingInfoHandler.cs +++ b/ErsatzTV.Application/Troubleshooting/Queries/GetTroubleshootingInfoHandler.cs @@ -74,6 +74,7 @@ public class GetTroubleshootingInfoHandler : IRequestHandler Channels, List Watermarks, bool AviSynthDemuxer, + bool AviSynthInstalled, string NvidiaCapabilities, string QsvCapabilities, string VaapiCapabilities, diff --git a/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs b/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs index 943f4da39..c0b4e4d51 100644 --- a/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs +++ b/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs @@ -152,13 +152,13 @@ public partial class HardwareCapabilitiesFactory( // if we don't have a list of cuda devices, fall back to ffmpeg check string[] arguments = - { + [ "-f", "lavfi", "-i", "nullsrc", "-c:v", "h264_nvenc", "-gpu", "list", "-f", "null", "-" - }; + ]; BufferedCommandResult result = await Cli.Wrap(ffmpegPath) .WithArguments(arguments) @@ -310,6 +310,26 @@ public partial class HardwareCapabilitiesFactory( public List GetVideoToolboxEncoders() => VideoToolboxUtil.GetAvailableEncoders(logger); + public void SetAviSynthInstalled(bool aviSynthInstalled) + { + var cacheKey = string.Format( + CultureInfo.InvariantCulture, + FFmpegCapabilitiesCacheKeyFormat, + "avisynth_installed"); + + memoryCache.Set(cacheKey, aviSynthInstalled); + } + + public bool IsAviSynthInstalled() + { + var cacheKey = string.Format( + CultureInfo.InvariantCulture, + FFmpegCapabilitiesCacheKeyFormat, + "avisynth_installed"); + + return memoryCache.TryGetValue(cacheKey, out bool installed) && installed; + } + private async Task> GetFFmpegCapabilities( string ffmpegPath, string capabilities, @@ -322,7 +342,7 @@ public partial class HardwareCapabilitiesFactory( return cachedCapabilities; } - string[] arguments = { "-hide_banner", $"-{capabilities}" }; + string[] arguments = ["-hide_banner", $"-{capabilities}"]; BufferedCommandResult result = await Cli.Wrap(ffmpegPath) .WithArguments(arguments) @@ -351,7 +371,7 @@ public partial class HardwareCapabilitiesFactory( return cachedCapabilities; } - string[] arguments = { "-hide_banner", "-h", "long" }; + string[] arguments = ["-hide_banner", "-h", "long"]; BufferedCommandResult result = await Cli.Wrap(ffmpegPath) .WithArguments(arguments) @@ -380,7 +400,7 @@ public partial class HardwareCapabilitiesFactory( return cachedCapabilities; } - string[] arguments = { "-hide_banner", "-formats" }; + string[] arguments = ["-hide_banner", "-formats"]; BufferedCommandResult result = await Cli.Wrap(ffmpegPath) .WithArguments(arguments) diff --git a/ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilitiesFactory.cs b/ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilitiesFactory.cs index 9adcf8d2d..c7aef1a67 100644 --- a/ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilitiesFactory.cs +++ b/ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilitiesFactory.cs @@ -31,4 +31,8 @@ public interface IHardwareCapabilitiesFactory List GetVideoToolboxDecoders(); List GetVideoToolboxEncoders(); + + void SetAviSynthInstalled(bool aviSynthInstalled); + + bool IsAviSynthInstalled(); } diff --git a/ErsatzTV/ErsatzTV.csproj b/ErsatzTV/ErsatzTV.csproj index fd0ae2f8b..283ca9d02 100644 --- a/ErsatzTV/ErsatzTV.csproj +++ b/ErsatzTV/ErsatzTV.csproj @@ -104,6 +104,7 @@ + diff --git a/ErsatzTV/Pages/Troubleshooting/Troubleshooting.razor b/ErsatzTV/Pages/Troubleshooting/Troubleshooting.razor index bd9daa441..b0cf038a0 100644 --- a/ErsatzTV/Pages/Troubleshooting/Troubleshooting.razor +++ b/ErsatzTV/Pages/Troubleshooting/Troubleshooting.razor @@ -159,7 +159,8 @@ info.FFmpegSettings, AviSynth = new { - Demuxer = info.AviSynthDemuxer + Demuxer = info.AviSynthDemuxer, + Installed = info.AviSynthInstalled }, info.Channels, info.FFmpegProfiles diff --git a/ErsatzTV/Resources/test.avs b/ErsatzTV/Resources/test.avs new file mode 100644 index 000000000..5e656113d --- /dev/null +++ b/ErsatzTV/Resources/test.avs @@ -0,0 +1,2 @@ +ColorBars(width=640, height=360, pixel_type="YV12") +Trim(0, 299) diff --git a/ErsatzTV/Services/RunOnce/ResourceExtractorService.cs b/ErsatzTV/Services/RunOnce/ResourceExtractorService.cs index e5e02b09c..e95f9075f 100644 --- a/ErsatzTV/Services/RunOnce/ResourceExtractorService.cs +++ b/ErsatzTV/Services/RunOnce/ResourceExtractorService.cs @@ -26,6 +26,7 @@ public class ResourceExtractorService : BackgroundService await ExtractResource(assembly, "ErsatzTV.png", stoppingToken); await ExtractResource(assembly, "sequential-schedule.schema.json", stoppingToken); await ExtractResource(assembly, "sequential-schedule-import.schema.json", stoppingToken); + await ExtractResource(assembly, "test.avs", stoppingToken); await ExtractFontResource(assembly, "Sen.ttf", stoppingToken); await ExtractFontResource(assembly, "Roboto-Regular.ttf", stoppingToken);