using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Extensions; using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Application.Streaming; public abstract class FFmpegProcessHandler : IRequestHandler> where T : FFmpegProcessRequest { private readonly IDbContextFactory _dbContextFactory; protected FFmpegProcessHandler(IDbContextFactory dbContextFactory) => _dbContextFactory = dbContextFactory; public async Task> Handle(T request, CancellationToken cancellationToken) { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); Validation> validation = await Validate( dbContext, request, cancellationToken); return await validation.Match( tuple => GetProcess( dbContext, request with { Now = request.Now - (tuple.Item1.PlayoutOffset ?? TimeSpan.Zero) }, tuple.Item1, tuple.Item2, tuple.Item3, cancellationToken), error => Task.FromResult>(error.Join())); } protected abstract Task> GetProcess( TvContext dbContext, T request, Channel channel, string ffmpegPath, string ffprobePath, CancellationToken cancellationToken); private static async Task>> Validate( TvContext dbContext, T request, CancellationToken cancellationToken) => (await ChannelMustExist(dbContext, request, cancellationToken), await FFmpegPathMustExist(dbContext, cancellationToken), await FFprobePathMustExist(dbContext, cancellationToken)) .Apply((channel, ffmpegPath, ffprobePath) => Tuple(channel, ffmpegPath, ffprobePath)); private static Task> ChannelMustExist( TvContext dbContext, T request, CancellationToken cancellationToken) => dbContext.Channels .Include(c => c.FFmpegProfile) .ThenInclude(p => p.Resolution) .Include(c => c.Artwork) .Include(c => c.Watermark) .SelectOneAsync(c => c.Number, c => c.Number == request.ChannelNumber, cancellationToken) .MapT(channel => { channel.StreamingMode = request.Mode; return channel; }) .Map(o => o.ToValidation($"Channel number {request.ChannelNumber} does not exist.")); private static Task> FFmpegPathMustExist( TvContext dbContext, CancellationToken cancellationToken) => dbContext.ConfigElements.GetValue(ConfigElementKey.FFmpegPath, cancellationToken) .FilterT(File.Exists) .Map(maybePath => maybePath.ToValidation("FFmpeg path does not exist on filesystem")); private static Task> FFprobePathMustExist( TvContext dbContext, CancellationToken cancellationToken) => dbContext.ConfigElements.GetValue(ConfigElementKey.FFprobePath, cancellationToken) .FilterT(File.Exists) .Map(maybePath => maybePath.ToValidation("FFprobe path does not exist on filesystem")); }