using ErsatzTV.Core.Domain; using ErsatzTV.Core.Extensions; using ErsatzTV.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace ErsatzTV.Application.Channels; public class GetChannelFramerateHandler : IRequestHandler> { private readonly IDbContextFactory _dbContextFactory; private readonly ILogger _logger; public GetChannelFramerateHandler( IDbContextFactory dbContextFactory, ILogger logger) { _dbContextFactory = dbContextFactory; _logger = logger; } public async Task> Handle(GetChannelFramerate request, CancellationToken cancellationToken) { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); FFmpegProfile ffmpegProfile = await dbContext.Channels .Filter(c => c.Number == request.ChannelNumber) .Include(c => c.FFmpegProfile) .Map(c => c.FFmpegProfile) .SingleAsync(cancellationToken); if (!ffmpegProfile.NormalizeFramerate) { return Option.None; } // TODO: expand to check everything in collection rather than what's scheduled? _logger.LogDebug("Checking frame rates for channel {ChannelNumber}", request.ChannelNumber); List playouts = await dbContext.Playouts .Include(p => p.Items) .ThenInclude(pi => pi.MediaItem) .ThenInclude(mi => (mi as Movie).MediaVersions) .Include(p => p.Items) .ThenInclude(pi => pi.MediaItem) .ThenInclude(mi => (mi as Episode).MediaVersions) .Include(p => p.Items) .ThenInclude(pi => pi.MediaItem) .ThenInclude(mi => (mi as Song).MediaVersions) .Include(p => p.Items) .ThenInclude(pi => pi.MediaItem) .ThenInclude(mi => (mi as MusicVideo).MediaVersions) .Include(p => p.Items) .ThenInclude(pi => pi.MediaItem) .ThenInclude(mi => (mi as OtherVideo).MediaVersions) .Filter(p => p.Channel.Number == request.ChannelNumber) .ToListAsync(cancellationToken); var frameRates = playouts.Map(p => p.Items.Map(i => i.MediaItem.GetHeadVersion())) .Flatten() .Map(mv => mv.RFrameRate) .ToList(); var distinct = frameRates.Distinct().ToList(); if (distinct.Count > 1) { // TODO: something more intelligent than minimum framerate? int result = frameRates.Map(ParseFrameRate).Min(); if (result < 24) { _logger.LogInformation( "Normalizing frame rate for channel {ChannelNumber} from {Distinct} to {FrameRate} instead of min value {MinFrameRate}", request.ChannelNumber, distinct, 24, result); return 24; } _logger.LogInformation( "Normalizing frame rate for channel {ChannelNumber} from {Distinct} to {FrameRate}", request.ChannelNumber, distinct, result); return result; } if (distinct.Count != 0) { _logger.LogInformation( "All content on channel {ChannelNumber} has the same frame rate of {FrameRate}; will not normalize", request.ChannelNumber, distinct[0]); } else { _logger.LogInformation( "No content on channel {ChannelNumber} has frame rate information; will not normalize", request.ChannelNumber); } return None; } private int ParseFrameRate(string frameRate) { if (!int.TryParse(frameRate, out int fr)) { string[] split = (frameRate ?? string.Empty).Split("/"); if (int.TryParse(split[0], out int left) && int.TryParse(split[1], out int right)) { fr = (int)Math.Round(left / (double)right); } else { fr = 24; } } return fr; } }