diff --git a/CHANGELOG.md b/CHANGELOG.md index 3caa2b92f..4ad5e0316 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added - Perform additional duration analysis on files with missing duration metadata +- Add `nouveau` VAAPI driver option ## [0.4.3-alpha] - 2022-03-05 ### Fixed diff --git a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs index 44c9d5a40..7532c0f86 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs @@ -391,6 +391,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService return "iHD"; case VaapiDriver.RadeonSI: return "radeonsi"; + case VaapiDriver.Nouveau: + return "nouveau"; } } diff --git a/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs b/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs index daf684036..ce36f881a 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs @@ -682,6 +682,9 @@ internal class FFmpegProcessBuilder case VaapiDriver.RadeonSI: startInfo.EnvironmentVariables["LIBVA_DRIVER_NAME"] = "radeonsi"; break; + case VaapiDriver.Nouveau: + startInfo.EnvironmentVariables["LIBVA_DRIVER_NAME"] = "nouveau"; + break; } } diff --git a/ErsatzTV.Core/FFmpeg/VaapiDriver.cs b/ErsatzTV.Core/FFmpeg/VaapiDriver.cs index 177314b90..2631a748a 100644 --- a/ErsatzTV.Core/FFmpeg/VaapiDriver.cs +++ b/ErsatzTV.Core/FFmpeg/VaapiDriver.cs @@ -8,5 +8,6 @@ public enum VaapiDriver Default = 0, iHD = 1, i965 = 2, - RadeonSI = 3 + RadeonSI = 3, + Nouveau = 4 } \ No newline at end of file diff --git a/ErsatzTV/Services/EmbyService.cs b/ErsatzTV/Services/EmbyService.cs index 6265db878..ed56341f9 100644 --- a/ErsatzTV/Services/EmbyService.cs +++ b/ErsatzTV/Services/EmbyService.cs @@ -26,61 +26,68 @@ public class EmbyService : BackgroundService protected override async Task ExecuteAsync(CancellationToken cancellationToken) { - if (!File.Exists(FileSystemLayout.EmbySecretsPath)) + try { - await File.WriteAllTextAsync(FileSystemLayout.EmbySecretsPath, "{}", cancellationToken); - } + if (!File.Exists(FileSystemLayout.EmbySecretsPath)) + { + await File.WriteAllTextAsync(FileSystemLayout.EmbySecretsPath, "{}", cancellationToken); + } - _logger.LogInformation( - "Emby service started; secrets are at {EmbySecretsPath}", - FileSystemLayout.EmbySecretsPath); + _logger.LogInformation( + "Emby service started; secrets are at {EmbySecretsPath}", + FileSystemLayout.EmbySecretsPath); - // synchronize sources on startup - await SynchronizeSources(new SynchronizeEmbyMediaSources(), cancellationToken); + // synchronize sources on startup + await SynchronizeSources(new SynchronizeEmbyMediaSources(), cancellationToken); - await foreach (IEmbyBackgroundServiceRequest request in _channel.ReadAllAsync(cancellationToken)) - { - try - { - Task requestTask; - switch (request) - { - case SynchronizeEmbyMediaSources synchronizeEmbyMediaSources: - requestTask = SynchronizeSources(synchronizeEmbyMediaSources, cancellationToken); - break; - // case SynchronizeEmbyAdminUserId synchronizeEmbyAdminUserId: - // requestTask = SynchronizeAdminUserId(synchronizeEmbyAdminUserId, cancellationToken); - // break; - case SynchronizeEmbyLibraries synchronizeEmbyLibraries: - requestTask = SynchronizeLibraries(synchronizeEmbyLibraries, cancellationToken); - break; - case ISynchronizeEmbyLibraryById synchronizeEmbyLibraryById: - requestTask = SynchronizeEmbyLibrary(synchronizeEmbyLibraryById, cancellationToken); - break; - default: - throw new NotSupportedException($"Unsupported request type: {request.GetType().Name}"); - } - - await requestTask; - } - catch (Exception ex) + await foreach (IEmbyBackgroundServiceRequest request in _channel.ReadAllAsync(cancellationToken)) { - _logger.LogWarning(ex, "Failed to process Emby background service request"); - try { - using (IServiceScope scope = _serviceScopeFactory.CreateScope()) + Task requestTask; + switch (request) { - IClient client = scope.ServiceProvider.GetRequiredService(); - client.Notify(ex); + case SynchronizeEmbyMediaSources synchronizeEmbyMediaSources: + requestTask = SynchronizeSources(synchronizeEmbyMediaSources, cancellationToken); + break; + // case SynchronizeEmbyAdminUserId synchronizeEmbyAdminUserId: + // requestTask = SynchronizeAdminUserId(synchronizeEmbyAdminUserId, cancellationToken); + // break; + case SynchronizeEmbyLibraries synchronizeEmbyLibraries: + requestTask = SynchronizeLibraries(synchronizeEmbyLibraries, cancellationToken); + break; + case ISynchronizeEmbyLibraryById synchronizeEmbyLibraryById: + requestTask = SynchronizeEmbyLibrary(synchronizeEmbyLibraryById, cancellationToken); + break; + default: + throw new NotSupportedException($"Unsupported request type: {request.GetType().Name}"); } + + await requestTask; } - catch (Exception) + catch (Exception ex) { - // do nothing + _logger.LogWarning(ex, "Failed to process Emby background service request"); + + try + { + using (IServiceScope scope = _serviceScopeFactory.CreateScope()) + { + IClient client = scope.ServiceProvider.GetRequiredService(); + client.Notify(ex); + } + } + catch (Exception) + { + // do nothing + } } } } + catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException) + { + _logger.LogInformation("Emby service shutting down"); + } } private async Task SynchronizeSources( diff --git a/ErsatzTV/Services/FFmpegWorkerService.cs b/ErsatzTV/Services/FFmpegWorkerService.cs index 523642a55..bd706a46d 100644 --- a/ErsatzTV/Services/FFmpegWorkerService.cs +++ b/ErsatzTV/Services/FFmpegWorkerService.cs @@ -27,40 +27,47 @@ public class FFmpegWorkerService : BackgroundService protected override async Task ExecuteAsync(CancellationToken cancellationToken) { - _logger.LogInformation("FFmpeg worker service started"); - - await foreach (IFFmpegWorkerRequest request in _channel.ReadAllAsync(cancellationToken)) + try { - using IServiceScope scope = _serviceScopeFactory.CreateScope(); + _logger.LogInformation("FFmpeg worker service started"); - try + await foreach (IFFmpegWorkerRequest request in _channel.ReadAllAsync(cancellationToken)) { + using IServiceScope scope = _serviceScopeFactory.CreateScope(); - switch (request) - { - case TouchFFmpegSession touchFFmpegSession: - foreach (DirectoryInfo parent in Optional(Directory.GetParent(touchFFmpegSession.Path))) - { - _ffmpegSegmenterService.TouchChannel(parent.Name); - } - - break; - } - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to handle ffmpeg worker request"); - try { - IClient client = scope.ServiceProvider.GetRequiredService(); - client.Notify(ex); + + switch (request) + { + case TouchFFmpegSession touchFFmpegSession: + foreach (DirectoryInfo parent in Optional(Directory.GetParent(touchFFmpegSession.Path))) + { + _ffmpegSegmenterService.TouchChannel(parent.Name); + } + + break; + } } - catch (Exception) + catch (Exception ex) { - // do nothing + _logger.LogWarning(ex, "Failed to handle ffmpeg worker request"); + + try + { + IClient client = scope.ServiceProvider.GetRequiredService(); + client.Notify(ex); + } + catch (Exception) + { + // do nothing + } } } } + catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException) + { + _logger.LogInformation("FFmpeg worker service shutting down"); + } } } \ No newline at end of file diff --git a/ErsatzTV/Services/JellyfinService.cs b/ErsatzTV/Services/JellyfinService.cs index 47eeb5dd2..32b8548ff 100644 --- a/ErsatzTV/Services/JellyfinService.cs +++ b/ErsatzTV/Services/JellyfinService.cs @@ -26,61 +26,68 @@ public class JellyfinService : BackgroundService protected override async Task ExecuteAsync(CancellationToken cancellationToken) { - if (!File.Exists(FileSystemLayout.JellyfinSecretsPath)) + try { - await File.WriteAllTextAsync(FileSystemLayout.JellyfinSecretsPath, "{}", cancellationToken); - } + if (!File.Exists(FileSystemLayout.JellyfinSecretsPath)) + { + await File.WriteAllTextAsync(FileSystemLayout.JellyfinSecretsPath, "{}", cancellationToken); + } - _logger.LogInformation( - "Jellyfin service started; secrets are at {JellyfinSecretsPath}", - FileSystemLayout.JellyfinSecretsPath); + _logger.LogInformation( + "Jellyfin service started; secrets are at {JellyfinSecretsPath}", + FileSystemLayout.JellyfinSecretsPath); - // synchronize sources on startup - await SynchronizeSources(new SynchronizeJellyfinMediaSources(), cancellationToken); + // synchronize sources on startup + await SynchronizeSources(new SynchronizeJellyfinMediaSources(), cancellationToken); - await foreach (IJellyfinBackgroundServiceRequest request in _channel.ReadAllAsync(cancellationToken)) - { - try - { - Task requestTask; - switch (request) - { - case SynchronizeJellyfinMediaSources synchronizeJellyfinMediaSources: - requestTask = SynchronizeSources(synchronizeJellyfinMediaSources, cancellationToken); - break; - case SynchronizeJellyfinAdminUserId synchronizeJellyfinAdminUserId: - requestTask = SynchronizeAdminUserId(synchronizeJellyfinAdminUserId, cancellationToken); - break; - case SynchronizeJellyfinLibraries synchronizeJellyfinLibraries: - requestTask = SynchronizeLibraries(synchronizeJellyfinLibraries, cancellationToken); - break; - case ISynchronizeJellyfinLibraryById synchronizeJellyfinLibraryById: - requestTask = SynchronizeJellyfinLibrary(synchronizeJellyfinLibraryById, cancellationToken); - break; - default: - throw new NotSupportedException($"Unsupported request type: {request.GetType().Name}"); - } - - await requestTask; - } - catch (Exception ex) + await foreach (IJellyfinBackgroundServiceRequest request in _channel.ReadAllAsync(cancellationToken)) { - _logger.LogWarning(ex, "Failed to process Jellyfin background service request"); - try { - using (IServiceScope scope = _serviceScopeFactory.CreateScope()) + Task requestTask; + switch (request) { - IClient client = scope.ServiceProvider.GetRequiredService(); - client.Notify(ex); + case SynchronizeJellyfinMediaSources synchronizeJellyfinMediaSources: + requestTask = SynchronizeSources(synchronizeJellyfinMediaSources, cancellationToken); + break; + case SynchronizeJellyfinAdminUserId synchronizeJellyfinAdminUserId: + requestTask = SynchronizeAdminUserId(synchronizeJellyfinAdminUserId, cancellationToken); + break; + case SynchronizeJellyfinLibraries synchronizeJellyfinLibraries: + requestTask = SynchronizeLibraries(synchronizeJellyfinLibraries, cancellationToken); + break; + case ISynchronizeJellyfinLibraryById synchronizeJellyfinLibraryById: + requestTask = SynchronizeJellyfinLibrary(synchronizeJellyfinLibraryById, cancellationToken); + break; + default: + throw new NotSupportedException($"Unsupported request type: {request.GetType().Name}"); } + + await requestTask; } - catch (Exception) + catch (Exception ex) { - // do nothing + _logger.LogWarning(ex, "Failed to process Jellyfin background service request"); + + try + { + using (IServiceScope scope = _serviceScopeFactory.CreateScope()) + { + IClient client = scope.ServiceProvider.GetRequiredService(); + client.Notify(ex); + } + } + catch (Exception) + { + // do nothing + } } } } + catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException) + { + _logger.LogInformation("Jellyfin service shutting down"); + } } private async Task SynchronizeSources( diff --git a/ErsatzTV/Services/PlexService.cs b/ErsatzTV/Services/PlexService.cs index 04b1a8e32..da0f0a3c2 100644 --- a/ErsatzTV/Services/PlexService.cs +++ b/ErsatzTV/Services/PlexService.cs @@ -26,61 +26,68 @@ public class PlexService : BackgroundService protected override async Task ExecuteAsync(CancellationToken cancellationToken) { - if (!File.Exists(FileSystemLayout.PlexSecretsPath)) + try { - await File.WriteAllTextAsync(FileSystemLayout.PlexSecretsPath, "{}", cancellationToken); - } + if (!File.Exists(FileSystemLayout.PlexSecretsPath)) + { + await File.WriteAllTextAsync(FileSystemLayout.PlexSecretsPath, "{}", cancellationToken); + } - _logger.LogInformation( - "Plex service started; secrets are at {PlexSecretsPath}", - FileSystemLayout.PlexSecretsPath); + _logger.LogInformation( + "Plex service started; secrets are at {PlexSecretsPath}", + FileSystemLayout.PlexSecretsPath); - // synchronize sources on startup - await SynchronizeSources(new SynchronizePlexMediaSources(), cancellationToken); + // synchronize sources on startup + await SynchronizeSources(new SynchronizePlexMediaSources(), cancellationToken); - await foreach (IPlexBackgroundServiceRequest request in _channel.ReadAllAsync(cancellationToken)) - { - try - { - Task requestTask; - switch (request) - { - case TryCompletePlexPinFlow pinRequest: - requestTask = CompletePinFlow(pinRequest, cancellationToken); - break; - case SynchronizePlexMediaSources sourcesRequest: - requestTask = SynchronizeSources(sourcesRequest, cancellationToken); - break; - case SynchronizePlexLibraries synchronizePlexLibrariesRequest: - requestTask = SynchronizeLibraries(synchronizePlexLibrariesRequest, cancellationToken); - break; - case ISynchronizePlexLibraryById synchronizePlexLibraryById: - requestTask = SynchronizePlexLibrary(synchronizePlexLibraryById, cancellationToken); - break; - default: - throw new NotSupportedException($"Unsupported request type: {request.GetType().Name}"); - } - - await requestTask; - } - catch (Exception ex) + await foreach (IPlexBackgroundServiceRequest request in _channel.ReadAllAsync(cancellationToken)) { - _logger.LogWarning(ex, "Failed to process plex background service request"); - try { - using (IServiceScope scope = _serviceScopeFactory.CreateScope()) + Task requestTask; + switch (request) { - IClient client = scope.ServiceProvider.GetRequiredService(); - client.Notify(ex); + case TryCompletePlexPinFlow pinRequest: + requestTask = CompletePinFlow(pinRequest, cancellationToken); + break; + case SynchronizePlexMediaSources sourcesRequest: + requestTask = SynchronizeSources(sourcesRequest, cancellationToken); + break; + case SynchronizePlexLibraries synchronizePlexLibrariesRequest: + requestTask = SynchronizeLibraries(synchronizePlexLibrariesRequest, cancellationToken); + break; + case ISynchronizePlexLibraryById synchronizePlexLibraryById: + requestTask = SynchronizePlexLibrary(synchronizePlexLibraryById, cancellationToken); + break; + default: + throw new NotSupportedException($"Unsupported request type: {request.GetType().Name}"); } + + await requestTask; } - catch (Exception) + catch (Exception ex) { - // do nothing + _logger.LogWarning(ex, "Failed to process plex background service request"); + + try + { + using (IServiceScope scope = _serviceScopeFactory.CreateScope()) + { + IClient client = scope.ServiceProvider.GetRequiredService(); + client.Notify(ex); + } + } + catch (Exception) + { + // do nothing + } } } } + catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException) + { + _logger.LogInformation("Plex service shutting down"); + } } private async Task> SynchronizeSources( diff --git a/ErsatzTV/Services/SchedulerService.cs b/ErsatzTV/Services/SchedulerService.cs index 447ab1d76..10f8347db 100644 --- a/ErsatzTV/Services/SchedulerService.cs +++ b/ErsatzTV/Services/SchedulerService.cs @@ -51,8 +51,16 @@ public class SchedulerService : BackgroundService int currentMinutes = DateTime.Now.TimeOfDay.Minutes; int toWait = currentMinutes < 30 ? 30 - currentMinutes : 60 - currentMinutes; _logger.LogDebug("Scheduler sleeping for {Minutes} minutes", toWait); - await Task.Delay(TimeSpan.FromMinutes(toWait), cancellationToken); - + + try + { + await Task.Delay(TimeSpan.FromMinutes(toWait), cancellationToken); + } + catch (TaskCanceledException) + { + // do nothing + } + if (!cancellationToken.IsCancellationRequested) { var roundedMinute = (int)(Math.Round(DateTime.Now.Minute / 5.0) * 5); diff --git a/ErsatzTV/Services/WorkerService.cs b/ErsatzTV/Services/WorkerService.cs index 02705d1c6..0bc59e9df 100644 --- a/ErsatzTV/Services/WorkerService.cs +++ b/ErsatzTV/Services/WorkerService.cs @@ -29,74 +29,90 @@ public class WorkerService : BackgroundService protected override async Task ExecuteAsync(CancellationToken cancellationToken) { - _logger.LogInformation("Worker service started"); - - await foreach (IBackgroundServiceRequest request in _channel.ReadAllAsync(cancellationToken)) + try { - using IServiceScope scope = _serviceScopeFactory.CreateScope(); + _logger.LogInformation("Worker service started"); - try + await foreach (IBackgroundServiceRequest request in _channel.ReadAllAsync(cancellationToken)) { - IMediator mediator = scope.ServiceProvider.GetRequiredService(); - - switch (request) + if (cancellationToken.IsCancellationRequested) { - case BuildPlayout buildPlayout: - Either buildPlayoutResult = await mediator.Send( - buildPlayout, - cancellationToken); - buildPlayoutResult.BiIter( - _ => _logger.LogDebug("Built playout {PlayoutId}", buildPlayout.PlayoutId), - error => _logger.LogWarning( - "Unable to build playout {PlayoutId}: {Error}", - buildPlayout.PlayoutId, - error.Value)); - break; - case IScanLocalLibrary scanLocalLibrary: - Either scanResult = await mediator.Send( - scanLocalLibrary, - cancellationToken); - scanResult.BiIter( - name => _logger.LogDebug( - "Done scanning local library {Library}", - name), - error => _logger.LogWarning( - "Unable to scan local library {LibraryId}: {Error}", - scanLocalLibrary.LibraryId, - error.Value)); - break; - case RebuildSearchIndex rebuildSearchIndex: - await mediator.Send(rebuildSearchIndex, cancellationToken); - break; - case DeleteOrphanedArtwork deleteOrphanedArtwork: - _logger.LogInformation("Deleting orphaned artwork from the database"); - await mediator.Send(deleteOrphanedArtwork, cancellationToken); - break; - case AddTraktList addTraktList: - await mediator.Send(addTraktList, cancellationToken); - break; - case DeleteTraktList deleteTraktList: - await mediator.Send(deleteTraktList, cancellationToken); - break; - case MatchTraktListItems matchTraktListItems: - await mediator.Send(matchTraktListItems, cancellationToken); - break; + break; } - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to process background service request"); + + using IServiceScope scope = _serviceScopeFactory.CreateScope(); try { - IClient client = scope.ServiceProvider.GetRequiredService(); - client.Notify(ex); + IMediator mediator = scope.ServiceProvider.GetRequiredService(); + + switch (request) + { + case BuildPlayout buildPlayout: + Either buildPlayoutResult = await mediator.Send( + buildPlayout, + cancellationToken); + buildPlayoutResult.BiIter( + _ => _logger.LogDebug("Built playout {PlayoutId}", buildPlayout.PlayoutId), + error => _logger.LogWarning( + "Unable to build playout {PlayoutId}: {Error}", + buildPlayout.PlayoutId, + error.Value)); + break; + case IScanLocalLibrary scanLocalLibrary: + Either scanResult = await mediator.Send( + scanLocalLibrary, + cancellationToken); + scanResult.BiIter( + name => _logger.LogDebug( + "Done scanning local library {Library}", + name), + error => _logger.LogWarning( + "Unable to scan local library {LibraryId}: {Error}", + scanLocalLibrary.LibraryId, + error.Value)); + break; + case RebuildSearchIndex rebuildSearchIndex: + await mediator.Send(rebuildSearchIndex, cancellationToken); + break; + case DeleteOrphanedArtwork deleteOrphanedArtwork: + _logger.LogInformation("Deleting orphaned artwork from the database"); + await mediator.Send(deleteOrphanedArtwork, cancellationToken); + break; + case AddTraktList addTraktList: + await mediator.Send(addTraktList, cancellationToken); + break; + case DeleteTraktList deleteTraktList: + await mediator.Send(deleteTraktList, cancellationToken); + break; + case MatchTraktListItems matchTraktListItems: + await mediator.Send(matchTraktListItems, cancellationToken); + break; + } } - catch (Exception) + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) { - // do nothing + // this can happen when we're shutting down + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to process background service request"); + + try + { + IClient client = scope.ServiceProvider.GetRequiredService(); + client.Notify(ex); + } + catch (Exception) + { + // do nothing + } } } } + catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException) + { + _logger.LogInformation("Worker service shutting down"); + } } } \ No newline at end of file