From 44e90b0eccdc29ad8b691807af622a648284ad5f Mon Sep 17 00:00:00 2001 From: Jason Dove Date: Sat, 5 Mar 2022 18:21:53 -0600 Subject: [PATCH] more bug reporting (#679) --- .../Commands/CreateFillerPresetHandler.cs | 10 +- .../Playouts/Commands/BuildPlayoutHandler.cs | 23 +- ...layoutItemProcessByChannelNumberHandler.cs | 275 +++++++++--------- .../FFmpeg/TranscodingTests.cs | 2 +- ErsatzTV.Core/Metadata/LocalFileSystem.cs | 14 +- .../Metadata/TelevisionFolderScanner.cs | 7 + 6 files changed, 180 insertions(+), 151 deletions(-) diff --git a/ErsatzTV.Application/Filler/Commands/CreateFillerPresetHandler.cs b/ErsatzTV.Application/Filler/Commands/CreateFillerPresetHandler.cs index 86f3540d7..1ed9835ba 100644 --- a/ErsatzTV.Application/Filler/Commands/CreateFillerPresetHandler.cs +++ b/ErsatzTV.Application/Filler/Commands/CreateFillerPresetHandler.cs @@ -1,4 +1,5 @@ -using ErsatzTV.Core; +using Bugsnag; +using ErsatzTV.Core; using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Infrastructure.Data; using Microsoft.EntityFrameworkCore; @@ -7,10 +8,12 @@ namespace ErsatzTV.Application.Filler; public class CreateFillerPresetHandler : IRequestHandler> { + private readonly IClient _client; private readonly IDbContextFactory _dbContextFactory; - public CreateFillerPresetHandler(IDbContextFactory dbContextFactory) + public CreateFillerPresetHandler(IClient client, IDbContextFactory dbContextFactory) { + _client = client; _dbContextFactory = dbContextFactory; } @@ -18,7 +21,7 @@ public class CreateFillerPresetHandler : IRequestHandler> { + private readonly IClient _client; private readonly IDbContextFactory _dbContextFactory; private readonly IPlayoutBuilder _playoutBuilder; - public BuildPlayoutHandler(IDbContextFactory dbContextFactory, IPlayoutBuilder playoutBuilder) + public BuildPlayoutHandler(IClient client, IDbContextFactory dbContextFactory, IPlayoutBuilder playoutBuilder) { + _client = client; _dbContextFactory = dbContextFactory; _playoutBuilder = playoutBuilder; } public async Task> Handle(BuildPlayout request, CancellationToken cancellationToken) { - await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); Validation validation = await Validate(dbContext, request); - return await LanguageExtensions.Apply(validation, playout => ApplyUpdateRequest(dbContext, request, playout)); + return await validation.Apply(playout => ApplyUpdateRequest(dbContext, request, playout)); } private async Task ApplyUpdateRequest(TvContext dbContext, BuildPlayout request, Playout playout) { - await _playoutBuilder.BuildPlayoutItems(playout, request.Rebuild); - await dbContext.SaveChangesAsync(); + try + { + await _playoutBuilder.BuildPlayoutItems(playout, request.Rebuild); + await dbContext.SaveChangesAsync(); + } + catch (Exception ex) + { + _client.Notify(ex); + } + return Unit.Default; } diff --git a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs index 00bbef6e2..bc0d4158f 100644 --- a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs +++ b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs @@ -18,8 +18,7 @@ using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Application.Streaming; -public class GetPlayoutItemProcessByChannelNumberHandler : - FFmpegProcessHandler +public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler { private readonly IEmbyPathReplacementService _embyPathReplacementService; private readonly IMediaCollectionRepository _mediaCollectionRepository; @@ -62,7 +61,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : string ffmpegPath) { DateTimeOffset now = request.Now; - + Either maybePlayoutItem = await dbContext.PlayoutItems .Include(i => i.MediaItem) .ThenInclude(mi => (mi as Episode).MediaVersions) @@ -108,144 +107,146 @@ public class GetPlayoutItemProcessByChannelNumberHandler : IFFmpegProcessService ffmpegProcessService = await _ffmpegProcessServiceFactory.GetService(); - return await maybePlayoutItem.Match( - async playoutItemWithPath => + foreach (PlayoutItemWithPath playoutItemWithPath in maybePlayoutItem.RightToSeq()) + { + MediaVersion version = playoutItemWithPath.PlayoutItem.MediaItem.GetHeadVersion(); + + string videoPath = playoutItemWithPath.Path; + MediaVersion videoVersion = version; + + string audioPath = playoutItemWithPath.Path; + MediaVersion audioVersion = version; + + Option maybeGlobalWatermark = await dbContext.ConfigElements + .GetValue(ConfigElementKey.FFmpegGlobalWatermarkId) + .BindT( + watermarkId => dbContext.ChannelWatermarks + .SelectOneAsync(w => w.Id, w => w.Id == watermarkId)); + + if (playoutItemWithPath.PlayoutItem.MediaItem is Song song) { - MediaVersion version = playoutItemWithPath.PlayoutItem.MediaItem.GetHeadVersion(); - - string videoPath = playoutItemWithPath.Path; - MediaVersion videoVersion = version; - - string audioPath = playoutItemWithPath.Path; - MediaVersion audioVersion = version; - - Option maybeGlobalWatermark = await dbContext.ConfigElements - .GetValue(ConfigElementKey.FFmpegGlobalWatermarkId) - .BindT( - watermarkId => dbContext.ChannelWatermarks - .SelectOneAsync(w => w.Id, w => w.Id == watermarkId)); - - if (playoutItemWithPath.PlayoutItem.MediaItem is Song song) - { - (videoPath, videoVersion) = await _songVideoGenerator.GenerateSongVideo( - song, - channel, - maybeGlobalWatermark, - ffmpegPath); - } - - bool saveReports = await dbContext.ConfigElements - .GetValue(ConfigElementKey.FFmpegSaveReports) - .Map(result => result.IfNone(false)); - - Process process = await ffmpegProcessService.ForPlayoutItem( - ffmpegPath, - saveReports, + (videoPath, videoVersion) = await _songVideoGenerator.GenerateSongVideo( + song, channel, - videoVersion, - audioVersion, - videoPath, - audioPath, - playoutItemWithPath.PlayoutItem.StartOffset, - playoutItemWithPath.PlayoutItem.FinishOffset, - request.StartAtZero ? playoutItemWithPath.PlayoutItem.StartOffset : now, maybeGlobalWatermark, - channel.FFmpegProfile.VaapiDriver, - channel.FFmpegProfile.VaapiDevice, - request.HlsRealtime, - playoutItemWithPath.PlayoutItem.FillerKind, - playoutItemWithPath.PlayoutItem.InPoint, - playoutItemWithPath.PlayoutItem.OutPoint, - request.PtsOffset, - request.TargetFramerate); - - var result = new PlayoutItemProcessModel(process, playoutItemWithPath.PlayoutItem.FinishOffset); - - return Right(result); - }, - async error => + ffmpegPath); + } + + bool saveReports = await dbContext.ConfigElements + .GetValue(ConfigElementKey.FFmpegSaveReports) + .Map(result => result.IfNone(false)); + + Process process = await ffmpegProcessService.ForPlayoutItem( + ffmpegPath, + saveReports, + channel, + videoVersion, + audioVersion, + videoPath, + audioPath, + playoutItemWithPath.PlayoutItem.StartOffset, + playoutItemWithPath.PlayoutItem.FinishOffset, + request.StartAtZero ? playoutItemWithPath.PlayoutItem.StartOffset : now, + maybeGlobalWatermark, + channel.FFmpegProfile.VaapiDriver, + channel.FFmpegProfile.VaapiDevice, + request.HlsRealtime, + playoutItemWithPath.PlayoutItem.FillerKind, + playoutItemWithPath.PlayoutItem.InPoint, + playoutItemWithPath.PlayoutItem.OutPoint, + request.PtsOffset, + request.TargetFramerate); + + var result = new PlayoutItemProcessModel(process, playoutItemWithPath.PlayoutItem.FinishOffset); + + return Right(result); + } + + foreach (BaseError error in maybePlayoutItem.LeftToSeq()) + { + var offlineTranscodeMessage = + $"offline image is unavailable because transcoding is disabled in ffmpeg profile '{channel.FFmpegProfile.Name}'"; + + Option maybeDuration = await Optional(channel.FFmpegProfile.Transcode) + .Where(transcode => transcode) + .Match( + _ => dbContext.PlayoutItems + .Filter(pi => pi.Playout.ChannelId == channel.Id) + .Filter(pi => pi.Start > now.UtcDateTime) + .OrderBy(pi => pi.Start) + .FirstOrDefaultAsync() + .Map(Optional) + .MapT(pi => pi.StartOffset - now), + () => Option.None.AsTask()); + + DateTimeOffset finish = maybeDuration.Match(d => now.Add(d), () => now); + + switch (error) { - var offlineTranscodeMessage = - $"offline image is unavailable because transcoding is disabled in ffmpeg profile '{channel.FFmpegProfile.Name}'"; - - Option maybeDuration = await Optional(channel.FFmpegProfile.Transcode) - .Where(transcode => transcode) - .Match( - _ => dbContext.PlayoutItems - .Filter(pi => pi.Playout.ChannelId == channel.Id) - .Filter(pi => pi.Start > now.UtcDateTime) - .OrderBy(pi => pi.Start) - .FirstOrDefaultAsync() - .Map(Optional) - .MapT(pi => pi.StartOffset - now), - () => Option.None.AsTask()); - - DateTimeOffset finish = maybeDuration.Match(d => now.Add(d), () => now); - - switch (error) - { - case UnableToLocatePlayoutItem: - if (channel.FFmpegProfile.Transcode) - { - Process errorProcess = await ffmpegProcessService.ForError( - ffmpegPath, - channel, - maybeDuration, - "Channel is Offline", - request.HlsRealtime, - request.PtsOffset); - - return new PlayoutItemProcessModel(errorProcess, finish); - } - else - { - var message = - $"Unable to locate playout item for channel {channel.Number}; {offlineTranscodeMessage}"; - - return BaseError.New(message); - } - case PlayoutItemDoesNotExistOnDisk: - if (channel.FFmpegProfile.Transcode) - { - Process errorProcess = await ffmpegProcessService.ForError( - ffmpegPath, - channel, - maybeDuration, - error.Value, - request.HlsRealtime, - request.PtsOffset); - - return new PlayoutItemProcessModel(errorProcess, finish); - } - else - { - var message = - $"Playout item does not exist on disk for channel {channel.Number}; {offlineTranscodeMessage}"; - - return BaseError.New(message); - } - default: - if (channel.FFmpegProfile.Transcode) - { - Process errorProcess = await ffmpegProcessService.ForError( - ffmpegPath, - channel, - maybeDuration, - "Channel is Offline", - request.HlsRealtime, - request.PtsOffset); - - return new PlayoutItemProcessModel(errorProcess, finish); - } - else - { - var message = - $"Unexpected error locating playout item for channel {channel.Number}; {offlineTranscodeMessage}"; - - return BaseError.New(message); - } - } - }); + case UnableToLocatePlayoutItem: + if (channel.FFmpegProfile.Transcode) + { + Process errorProcess = await ffmpegProcessService.ForError( + ffmpegPath, + channel, + maybeDuration, + "Channel is Offline", + request.HlsRealtime, + request.PtsOffset); + + return new PlayoutItemProcessModel(errorProcess, finish); + } + else + { + var message = + $"Unable to locate playout item for channel {channel.Number}; {offlineTranscodeMessage}"; + + return BaseError.New(message); + } + case PlayoutItemDoesNotExistOnDisk: + if (channel.FFmpegProfile.Transcode) + { + Process errorProcess = await ffmpegProcessService.ForError( + ffmpegPath, + channel, + maybeDuration, + error.Value, + request.HlsRealtime, + request.PtsOffset); + + return new PlayoutItemProcessModel(errorProcess, finish); + } + else + { + var message = + $"Playout item does not exist on disk for channel {channel.Number}; {offlineTranscodeMessage}"; + + return BaseError.New(message); + } + default: + if (channel.FFmpegProfile.Transcode) + { + Process errorProcess = await ffmpegProcessService.ForError( + ffmpegPath, + channel, + maybeDuration, + "Channel is Offline", + request.HlsRealtime, + request.PtsOffset); + + return new PlayoutItemProcessModel(errorProcess, finish); + } + else + { + var message = + $"Unexpected error locating playout item for channel {channel.Number}; {offlineTranscodeMessage}"; + + return BaseError.New(message); + } + } + } + + return BaseError.New($"Unexpected error locating playout item for channel {channel.Number}"); } private async Task> CheckForFallbackFiller( diff --git a/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs b/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs index ad0bd863e..aad93318a 100644 --- a/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs +++ b/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs @@ -277,7 +277,7 @@ public class TranscodingTests var localStatisticsProvider = new LocalStatisticsProvider( metadataRepository.Object, - new LocalFileSystem(LoggerFactory.CreateLogger()), + new LocalFileSystem(new Mock().Object, LoggerFactory.CreateLogger()), new Mock().Object, LoggerFactory.CreateLogger()); diff --git a/ErsatzTV.Core/Metadata/LocalFileSystem.cs b/ErsatzTV.Core/Metadata/LocalFileSystem.cs index c9fe0645a..78022ab50 100644 --- a/ErsatzTV.Core/Metadata/LocalFileSystem.cs +++ b/ErsatzTV.Core/Metadata/LocalFileSystem.cs @@ -1,4 +1,5 @@ -using ErsatzTV.Core.Domain; +using Bugsnag; +using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Metadata; using Microsoft.Extensions.Logging; @@ -6,10 +7,12 @@ namespace ErsatzTV.Core.Metadata; public class LocalFileSystem : ILocalFileSystem { + private readonly IClient _client; private readonly ILogger _logger; - public LocalFileSystem(ILogger logger) + public LocalFileSystem(IClient client, ILogger logger) { + _client = client; _logger = logger; } @@ -44,9 +47,10 @@ public class LocalFileSystem : ILocalFileSystem { return Directory.EnumerateDirectories(folder); } - catch + catch (Exception ex) { // do nothing + _client.Notify(ex); } } @@ -61,9 +65,10 @@ public class LocalFileSystem : ILocalFileSystem { return Directory.EnumerateFiles(folder, "*", SearchOption.TopDirectoryOnly); } - catch + catch (Exception ex) { // do nothing + _client.Notify(ex); } } @@ -92,6 +97,7 @@ public class LocalFileSystem : ILocalFileSystem } catch (Exception ex) { + _client.Notify(ex); return BaseError.New(ex.ToString()); } } diff --git a/ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs b/ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs index a36408670..753954e24 100644 --- a/ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs +++ b/ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs @@ -18,6 +18,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan private readonly ILocalMetadataProvider _localMetadataProvider; private readonly ILogger _logger; private readonly IMediator _mediator; + private readonly IClient _client; private readonly IMetadataRepository _metadataRepository; private readonly ISearchIndex _searchIndex; private readonly ISearchRepository _searchRepository; @@ -57,6 +58,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan _searchRepository = searchRepository; _libraryRepository = libraryRepository; _mediator = mediator; + _client = client; _logger = logger; } @@ -287,6 +289,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan } catch (Exception ex) { + _client.Notify(ex); return BaseError.New(ex.ToString()); } } @@ -348,6 +351,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan } catch (Exception ex) { + _client.Notify(ex); return BaseError.New(ex.ToString()); } } @@ -371,6 +375,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan } catch (Exception ex) { + _client.Notify(ex); return BaseError.New(ex.ToString()); } } @@ -390,6 +395,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan } catch (Exception ex) { + _client.Notify(ex); return BaseError.New(ex.ToString()); } } @@ -411,6 +417,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan } catch (Exception ex) { + _client.Notify(ex); return BaseError.New(ex.ToString()); } }