From e96e48777cc065fdcd4896018826fe82985a9d27 Mon Sep 17 00:00:00 2001 From: Jason Dove Date: Mon, 8 Mar 2021 05:16:56 -0600 Subject: [PATCH] use output duration flag (#49) * re-enable output duration flag * calculate appropriate duration for offline image --- ...etPlayoutItemProcessByChannelNumberHandler.cs | 11 ++++++----- ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs | 10 ++++++---- ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs | 14 ++++++++------ .../Repositories/IPlayoutRepository.cs | 1 + .../Data/Repositories/PlayoutRepository.cs | 16 +++++++++++++--- 5 files changed, 34 insertions(+), 18 deletions(-) diff --git a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs index bf22032e7..5145494d6 100644 --- a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs +++ b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs @@ -10,7 +10,6 @@ using ErsatzTV.Core.FFmpeg; using ErsatzTV.Core.Interfaces.Repositories; using LanguageExt; using Microsoft.Extensions.Logging; -using static LanguageExt.Prelude; namespace ErsatzTV.Application.Streaming.Queries { @@ -69,18 +68,20 @@ namespace ErsatzTV.Application.Streaming.Queries playoutItem.StartOffset, now); }, - () => + async () => { if (channel.FFmpegProfile.Transcode) { - return Right(_ffmpegProcessService.ForOfflineImage(ffmpegPath, channel)) - .AsTask(); + Option maybeDuration = await _playoutRepository.GetNextItemStart(channel.Id, now) + .MapT(nextStart => nextStart - now); + + return _ffmpegProcessService.ForOfflineImage(ffmpegPath, channel, maybeDuration); } var message = $"Unable to locate playout item for channel {channel.Number}; offline image is unavailable because transcoding is disabled in ffmpeg profile '{channel.FFmpegProfile.Name}'"; - return Left(BaseError.New(message)).AsTask(); + return BaseError.New(message); }); } diff --git a/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs b/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs index 9fbacad45..2d95f2472 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs @@ -227,10 +227,12 @@ namespace ErsatzTV.Core.FFmpeg "1:a"); } - public FFmpegProcessBuilder WithDuration(TimeSpan duration) => - // _arguments.Add("-t"); - // _arguments.Add($"{duration:c}"); - this; + public FFmpegProcessBuilder WithDuration(TimeSpan duration) + { + _arguments.Add("-t"); + _arguments.Add($"{duration:c}"); + return this; + } public FFmpegProcessBuilder WithFormat(string format) { diff --git a/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs b/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs index 3c8de1e34..f06d4aeec 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.FFmpeg; +using LanguageExt; namespace ErsatzTV.Core.FFmpeg { @@ -83,14 +84,14 @@ namespace ErsatzTV.Core.FFmpeg .Build(); } - public Process ForOfflineImage(string ffmpegPath, Channel channel) + public Process ForOfflineImage(string ffmpegPath, Channel channel, Option duration) { FFmpegPlaybackSettings playbackSettings = _playbackSettingsCalculator.CalculateErrorSettings(channel.FFmpegProfile); IDisplaySize desiredResolution = channel.FFmpegProfile.Resolution; - return new FFmpegProcessBuilder(ffmpegPath) + FFmpegProcessBuilder builder = new FFmpegProcessBuilder(ffmpegPath) .WithThreads(1) .WithQuiet() .WithFormatFlags(playbackSettings.FormatFlags) @@ -102,10 +103,11 @@ namespace ErsatzTV.Core.FFmpeg .WithPixfmt("yuv420p") .WithPlaybackArgs(playbackSettings) .WithMetadata(channel) - .WithFormat("mpegts") - .WithDuration(TimeSpan.FromSeconds(10)) // TODO: figure out when we're back online - .WithPipe() - .Build(); + .WithFormat("mpegts"); + + duration.IfSome(d => builder = builder.WithDuration(d)); + + return builder.WithPipe().Build(); } public Process ConcatChannel(string ffmpegPath, Channel channel, string scheme, string host) diff --git a/ErsatzTV.Core/Interfaces/Repositories/IPlayoutRepository.cs b/ErsatzTV.Core/Interfaces/Repositories/IPlayoutRepository.cs index b46430654..98985e99d 100644 --- a/ErsatzTV.Core/Interfaces/Repositories/IPlayoutRepository.cs +++ b/ErsatzTV.Core/Interfaces/Repositories/IPlayoutRepository.cs @@ -12,6 +12,7 @@ namespace ErsatzTV.Core.Interfaces.Repositories Task> Get(int id); Task> GetFull(int id); Task> GetPlayoutItem(int channelId, DateTimeOffset now); + Task> GetNextItemStart(int channelId, DateTimeOffset now); Task> GetPlayoutItems(int playoutId); Task> GetAll(); Task Update(Playout playout); diff --git a/ErsatzTV.Infrastructure/Data/Repositories/PlayoutRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/PlayoutRepository.cs index 28e940768..b8d8500cf 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/PlayoutRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/PlayoutRepository.cs @@ -44,8 +44,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories .OrderBy(p => p.Id) // https://github.com/dotnet/efcore/issues/22579#issuecomment-694772289 .SingleOrDefaultAsync(p => p.Id == id); - public async Task> GetPlayoutItem(int channelId, DateTimeOffset now) => - await _dbContext.PlayoutItems + public Task> GetPlayoutItem(int channelId, DateTimeOffset now) => + _dbContext.PlayoutItems .Where(pi => pi.Playout.ChannelId == channelId) .Where(pi => pi.Start <= now.UtcDateTime && pi.Finish > now.UtcDateTime) .Include(i => i.MediaItem) @@ -55,7 +55,17 @@ namespace ErsatzTV.Infrastructure.Data.Repositories .ThenInclude(mi => (mi as Movie).MediaVersions) .ThenInclude(mv => mv.MediaFiles) .AsNoTracking() - .SingleOrDefaultAsync(); + .SingleOrDefaultAsync() + .Map(Optional); + + public Task> GetNextItemStart(int channelId, DateTimeOffset now) => + _dbContext.PlayoutItems + .Where(pi => pi.Playout.ChannelId == channelId) + .Where(pi => pi.Finish > now.UtcDateTime) + .OrderBy(pi => pi.Finish) + .FirstOrDefaultAsync() + .Map(Optional) + .MapT(pi => pi.StartOffset); public Task> GetPlayoutItems(int playoutId) => _dbContext.PlayoutItems