Browse Source

more bug reporting (#679)

pull/680/head
Jason Dove 4 years ago committed by GitHub
parent
commit
44e90b0ecc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      ErsatzTV.Application/Filler/Commands/CreateFillerPresetHandler.cs
  2. 23
      ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs
  3. 273
      ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs
  4. 2
      ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs
  5. 14
      ErsatzTV.Core/Metadata/LocalFileSystem.cs
  6. 7
      ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs

10
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.Core.Domain.Filler;
using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -7,10 +8,12 @@ namespace ErsatzTV.Application.Filler;
public class CreateFillerPresetHandler : IRequestHandler<CreateFillerPreset, Either<BaseError, Unit>> public class CreateFillerPresetHandler : IRequestHandler<CreateFillerPreset, Either<BaseError, Unit>>
{ {
private readonly IClient _client;
private readonly IDbContextFactory<TvContext> _dbContextFactory; private readonly IDbContextFactory<TvContext> _dbContextFactory;
public CreateFillerPresetHandler(IDbContextFactory<TvContext> dbContextFactory) public CreateFillerPresetHandler(IClient client, IDbContextFactory<TvContext> dbContextFactory)
{ {
_client = client;
_dbContextFactory = dbContextFactory; _dbContextFactory = dbContextFactory;
} }
@ -18,7 +21,7 @@ public class CreateFillerPresetHandler : IRequestHandler<CreateFillerPreset, Eit
{ {
try try
{ {
await using TvContext dbContext = _dbContextFactory.CreateDbContext(); await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
var fillerPreset = new FillerPreset var fillerPreset = new FillerPreset
{ {
@ -42,6 +45,7 @@ public class CreateFillerPresetHandler : IRequestHandler<CreateFillerPreset, Eit
} }
catch (Exception ex) catch (Exception ex)
{ {
_client.Notify(ex);
return BaseError.New(ex.Message); return BaseError.New(ex.Message);
} }
} }

23
ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs

@ -1,4 +1,5 @@
using ErsatzTV.Core; using Bugsnag;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Interfaces.Scheduling;
using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Data;
@ -9,26 +10,36 @@ namespace ErsatzTV.Application.Playouts;
public class BuildPlayoutHandler : MediatR.IRequestHandler<BuildPlayout, Either<BaseError, Unit>> public class BuildPlayoutHandler : MediatR.IRequestHandler<BuildPlayout, Either<BaseError, Unit>>
{ {
private readonly IClient _client;
private readonly IDbContextFactory<TvContext> _dbContextFactory; private readonly IDbContextFactory<TvContext> _dbContextFactory;
private readonly IPlayoutBuilder _playoutBuilder; private readonly IPlayoutBuilder _playoutBuilder;
public BuildPlayoutHandler(IDbContextFactory<TvContext> dbContextFactory, IPlayoutBuilder playoutBuilder) public BuildPlayoutHandler(IClient client, IDbContextFactory<TvContext> dbContextFactory, IPlayoutBuilder playoutBuilder)
{ {
_client = client;
_dbContextFactory = dbContextFactory; _dbContextFactory = dbContextFactory;
_playoutBuilder = playoutBuilder; _playoutBuilder = playoutBuilder;
} }
public async Task<Either<BaseError, Unit>> Handle(BuildPlayout request, CancellationToken cancellationToken) public async Task<Either<BaseError, Unit>> Handle(BuildPlayout request, CancellationToken cancellationToken)
{ {
await using TvContext dbContext = _dbContextFactory.CreateDbContext(); await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, Playout> validation = await Validate(dbContext, request); Validation<BaseError, Playout> 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<Unit> ApplyUpdateRequest(TvContext dbContext, BuildPlayout request, Playout playout) private async Task<Unit> ApplyUpdateRequest(TvContext dbContext, BuildPlayout request, Playout playout)
{ {
await _playoutBuilder.BuildPlayoutItems(playout, request.Rebuild); try
await dbContext.SaveChangesAsync(); {
await _playoutBuilder.BuildPlayoutItems(playout, request.Rebuild);
await dbContext.SaveChangesAsync();
}
catch (Exception ex)
{
_client.Notify(ex);
}
return Unit.Default; return Unit.Default;
} }

273
ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs

@ -18,8 +18,7 @@ using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Application.Streaming; namespace ErsatzTV.Application.Streaming;
public class GetPlayoutItemProcessByChannelNumberHandler : public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<GetPlayoutItemProcessByChannelNumber>
FFmpegProcessHandler<GetPlayoutItemProcessByChannelNumber>
{ {
private readonly IEmbyPathReplacementService _embyPathReplacementService; private readonly IEmbyPathReplacementService _embyPathReplacementService;
private readonly IMediaCollectionRepository _mediaCollectionRepository; private readonly IMediaCollectionRepository _mediaCollectionRepository;
@ -108,144 +107,146 @@ public class GetPlayoutItemProcessByChannelNumberHandler :
IFFmpegProcessService ffmpegProcessService = await _ffmpegProcessServiceFactory.GetService(); IFFmpegProcessService ffmpegProcessService = await _ffmpegProcessServiceFactory.GetService();
return await maybePlayoutItem.Match( foreach (PlayoutItemWithPath playoutItemWithPath in maybePlayoutItem.RightToSeq())
async playoutItemWithPath => {
MediaVersion version = playoutItemWithPath.PlayoutItem.MediaItem.GetHeadVersion();
string videoPath = playoutItemWithPath.Path;
MediaVersion videoVersion = version;
string audioPath = playoutItemWithPath.Path;
MediaVersion audioVersion = version;
Option<ChannelWatermark> maybeGlobalWatermark = await dbContext.ConfigElements
.GetValue<int>(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(); (videoPath, videoVersion) = await _songVideoGenerator.GenerateSongVideo(
song,
string videoPath = playoutItemWithPath.Path;
MediaVersion videoVersion = version;
string audioPath = playoutItemWithPath.Path;
MediaVersion audioVersion = version;
Option<ChannelWatermark> maybeGlobalWatermark = await dbContext.ConfigElements
.GetValue<int>(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<bool>(ConfigElementKey.FFmpegSaveReports)
.Map(result => result.IfNone(false));
Process process = await ffmpegProcessService.ForPlayoutItem(
ffmpegPath,
saveReports,
channel, channel,
videoVersion,
audioVersion,
videoPath,
audioPath,
playoutItemWithPath.PlayoutItem.StartOffset,
playoutItemWithPath.PlayoutItem.FinishOffset,
request.StartAtZero ? playoutItemWithPath.PlayoutItem.StartOffset : now,
maybeGlobalWatermark, maybeGlobalWatermark,
channel.FFmpegProfile.VaapiDriver, ffmpegPath);
channel.FFmpegProfile.VaapiDevice, }
request.HlsRealtime,
playoutItemWithPath.PlayoutItem.FillerKind, bool saveReports = await dbContext.ConfigElements
playoutItemWithPath.PlayoutItem.InPoint, .GetValue<bool>(ConfigElementKey.FFmpegSaveReports)
playoutItemWithPath.PlayoutItem.OutPoint, .Map(result => result.IfNone(false));
request.PtsOffset,
request.TargetFramerate); Process process = await ffmpegProcessService.ForPlayoutItem(
ffmpegPath,
var result = new PlayoutItemProcessModel(process, playoutItemWithPath.PlayoutItem.FinishOffset); saveReports,
channel,
return Right<BaseError, PlayoutItemProcessModel>(result); videoVersion,
}, audioVersion,
async error => 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<BaseError, PlayoutItemProcessModel>(result);
}
foreach (BaseError error in maybePlayoutItem.LeftToSeq())
{
var offlineTranscodeMessage =
$"offline image is unavailable because transcoding is disabled in ffmpeg profile '{channel.FFmpegProfile.Name}'";
Option<TimeSpan> 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<TimeSpan>.None.AsTask());
DateTimeOffset finish = maybeDuration.Match(d => now.Add(d), () => now);
switch (error)
{ {
var offlineTranscodeMessage = case UnableToLocatePlayoutItem:
$"offline image is unavailable because transcoding is disabled in ffmpeg profile '{channel.FFmpegProfile.Name}'"; if (channel.FFmpegProfile.Transcode)
{
Option<TimeSpan> maybeDuration = await Optional(channel.FFmpegProfile.Transcode) Process errorProcess = await ffmpegProcessService.ForError(
.Where(transcode => transcode) ffmpegPath,
.Match( channel,
_ => dbContext.PlayoutItems maybeDuration,
.Filter(pi => pi.Playout.ChannelId == channel.Id) "Channel is Offline",
.Filter(pi => pi.Start > now.UtcDateTime) request.HlsRealtime,
.OrderBy(pi => pi.Start) request.PtsOffset);
.FirstOrDefaultAsync()
.Map(Optional) return new PlayoutItemProcessModel(errorProcess, finish);
.MapT(pi => pi.StartOffset - now), }
() => Option<TimeSpan>.None.AsTask()); else
{
DateTimeOffset finish = maybeDuration.Match(d => now.Add(d), () => now); var message =
$"Unable to locate playout item for channel {channel.Number}; {offlineTranscodeMessage}";
switch (error)
{ return BaseError.New(message);
case UnableToLocatePlayoutItem: }
if (channel.FFmpegProfile.Transcode) case PlayoutItemDoesNotExistOnDisk:
{ if (channel.FFmpegProfile.Transcode)
Process errorProcess = await ffmpegProcessService.ForError( {
ffmpegPath, Process errorProcess = await ffmpegProcessService.ForError(
channel, ffmpegPath,
maybeDuration, channel,
"Channel is Offline", maybeDuration,
request.HlsRealtime, error.Value,
request.PtsOffset); request.HlsRealtime,
request.PtsOffset);
return new PlayoutItemProcessModel(errorProcess, finish);
} return new PlayoutItemProcessModel(errorProcess, finish);
else }
{ else
var message = {
$"Unable to locate playout item for channel {channel.Number}; {offlineTranscodeMessage}"; var message =
$"Playout item does not exist on disk for channel {channel.Number}; {offlineTranscodeMessage}";
return BaseError.New(message);
} return BaseError.New(message);
case PlayoutItemDoesNotExistOnDisk: }
if (channel.FFmpegProfile.Transcode) default:
{ if (channel.FFmpegProfile.Transcode)
Process errorProcess = await ffmpegProcessService.ForError( {
ffmpegPath, Process errorProcess = await ffmpegProcessService.ForError(
channel, ffmpegPath,
maybeDuration, channel,
error.Value, maybeDuration,
request.HlsRealtime, "Channel is Offline",
request.PtsOffset); request.HlsRealtime,
request.PtsOffset);
return new PlayoutItemProcessModel(errorProcess, finish);
} return new PlayoutItemProcessModel(errorProcess, finish);
else }
{ else
var message = {
$"Playout item does not exist on disk for channel {channel.Number}; {offlineTranscodeMessage}"; var message =
$"Unexpected error locating playout item for channel {channel.Number}; {offlineTranscodeMessage}";
return BaseError.New(message);
} return BaseError.New(message);
default: }
if (channel.FFmpegProfile.Transcode) }
{ }
Process errorProcess = await ffmpegProcessService.ForError(
ffmpegPath, return BaseError.New($"Unexpected error locating playout item for channel {channel.Number}");
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);
}
}
});
} }
private async Task<Either<BaseError, PlayoutItemWithPath>> CheckForFallbackFiller( private async Task<Either<BaseError, PlayoutItemWithPath>> CheckForFallbackFiller(

2
ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs

@ -277,7 +277,7 @@ public class TranscodingTests
var localStatisticsProvider = new LocalStatisticsProvider( var localStatisticsProvider = new LocalStatisticsProvider(
metadataRepository.Object, metadataRepository.Object,
new LocalFileSystem(LoggerFactory.CreateLogger<LocalFileSystem>()), new LocalFileSystem(new Mock<IClient>().Object, LoggerFactory.CreateLogger<LocalFileSystem>()),
new Mock<IClient>().Object, new Mock<IClient>().Object,
LoggerFactory.CreateLogger<LocalStatisticsProvider>()); LoggerFactory.CreateLogger<LocalStatisticsProvider>());

14
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 ErsatzTV.Core.Interfaces.Metadata;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -6,10 +7,12 @@ namespace ErsatzTV.Core.Metadata;
public class LocalFileSystem : ILocalFileSystem public class LocalFileSystem : ILocalFileSystem
{ {
private readonly IClient _client;
private readonly ILogger<LocalFileSystem> _logger; private readonly ILogger<LocalFileSystem> _logger;
public LocalFileSystem(ILogger<LocalFileSystem> logger) public LocalFileSystem(IClient client, ILogger<LocalFileSystem> logger)
{ {
_client = client;
_logger = logger; _logger = logger;
} }
@ -44,9 +47,10 @@ public class LocalFileSystem : ILocalFileSystem
{ {
return Directory.EnumerateDirectories(folder); return Directory.EnumerateDirectories(folder);
} }
catch catch (Exception ex)
{ {
// do nothing // do nothing
_client.Notify(ex);
} }
} }
@ -61,9 +65,10 @@ public class LocalFileSystem : ILocalFileSystem
{ {
return Directory.EnumerateFiles(folder, "*", SearchOption.TopDirectoryOnly); return Directory.EnumerateFiles(folder, "*", SearchOption.TopDirectoryOnly);
} }
catch catch (Exception ex)
{ {
// do nothing // do nothing
_client.Notify(ex);
} }
} }
@ -92,6 +97,7 @@ public class LocalFileSystem : ILocalFileSystem
} }
catch (Exception ex) catch (Exception ex)
{ {
_client.Notify(ex);
return BaseError.New(ex.ToString()); return BaseError.New(ex.ToString());
} }
} }

7
ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs

@ -18,6 +18,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
private readonly ILocalMetadataProvider _localMetadataProvider; private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly ILogger<TelevisionFolderScanner> _logger; private readonly ILogger<TelevisionFolderScanner> _logger;
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly IClient _client;
private readonly IMetadataRepository _metadataRepository; private readonly IMetadataRepository _metadataRepository;
private readonly ISearchIndex _searchIndex; private readonly ISearchIndex _searchIndex;
private readonly ISearchRepository _searchRepository; private readonly ISearchRepository _searchRepository;
@ -57,6 +58,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
_searchRepository = searchRepository; _searchRepository = searchRepository;
_libraryRepository = libraryRepository; _libraryRepository = libraryRepository;
_mediator = mediator; _mediator = mediator;
_client = client;
_logger = logger; _logger = logger;
} }
@ -287,6 +289,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
} }
catch (Exception ex) catch (Exception ex)
{ {
_client.Notify(ex);
return BaseError.New(ex.ToString()); return BaseError.New(ex.ToString());
} }
} }
@ -348,6 +351,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
} }
catch (Exception ex) catch (Exception ex)
{ {
_client.Notify(ex);
return BaseError.New(ex.ToString()); return BaseError.New(ex.ToString());
} }
} }
@ -371,6 +375,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
} }
catch (Exception ex) catch (Exception ex)
{ {
_client.Notify(ex);
return BaseError.New(ex.ToString()); return BaseError.New(ex.ToString());
} }
} }
@ -390,6 +395,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
} }
catch (Exception ex) catch (Exception ex)
{ {
_client.Notify(ex);
return BaseError.New(ex.ToString()); return BaseError.New(ex.ToString());
} }
} }
@ -411,6 +417,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
} }
catch (Exception ex) catch (Exception ex)
{ {
_client.Notify(ex);
return BaseError.New(ex.ToString()); return BaseError.New(ex.ToString());
} }
} }

Loading…
Cancel
Save