diff --git a/ErsatzTV.Application/Playouts/Commands/SyncNextPlayoutHandler.cs b/ErsatzTV.Application/Playouts/Commands/SyncNextPlayoutHandler.cs index 9ac42766b..d709ed85a 100644 --- a/ErsatzTV.Application/Playouts/Commands/SyncNextPlayoutHandler.cs +++ b/ErsatzTV.Application/Playouts/Commands/SyncNextPlayoutHandler.cs @@ -6,7 +6,9 @@ using CliWrap; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Extensions; +using ErsatzTV.Core.FFmpeg; using ErsatzTV.Core.Interfaces.Emby; +using ErsatzTV.Core.Interfaces.FFmpeg; using ErsatzTV.Core.Interfaces.Jellyfin; using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Plex; @@ -14,7 +16,6 @@ using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -using PlayoutItem = ErsatzTV.Core.Domain.PlayoutItem; namespace ErsatzTV.Application.Playouts; @@ -24,6 +25,8 @@ public partial class SyncNextPlayoutHandler( IPlexPathReplacementService plexPathReplacementService, IJellyfinPathReplacementService jellyfinPathReplacementService, IEmbyPathReplacementService embyPathReplacementService, + ICustomStreamSelector customStreamSelector, + IFFmpegStreamSelector ffmpegStreamSelector, IDbContextFactory dbContextFactory, ILogger logger) : IRequestHandler @@ -223,6 +226,21 @@ public partial class SyncNextPlayoutHandler( }; } + maybeChannel = await dbContext.Channels + .AsNoTracking() + .SingleOrDefaultAsync(c => c.Number == channelNumber, cancellationToken); + foreach (Channel channel in maybeChannel) + { + var audioVersion = new MediaItemAudioVersion(playoutItem.MediaItem, headVersion); + await SelectTracks( + channel, + audioVersion, + nextPlayoutItem, + playoutItem.PreferredAudioLanguageCode ?? channel.PreferredAudioLanguageCode, + playoutItem.PreferredAudioTitle ?? channel.PreferredAudioTitle, + cancellationToken); + } + playout.Items.Add(nextPlayoutItem); } @@ -230,6 +248,63 @@ public partial class SyncNextPlayoutHandler( } } + private async Task SelectTracks( + Channel channel, + MediaItemAudioVersion audioVersion, + Core.Next.PlayoutItem nextPlayoutItem, + string preferredAudioLanguage, + string preferredAudioTitle, + CancellationToken cancellationToken) + { + // TODO: NEXT: support subtitles + List allSubtitles = []; + + Option maybeAudioStream = Option.None; + //Option maybeSubtitle = Option.None; + + if (channel.StreamSelectorMode is ChannelStreamSelectorMode.Custom) + { + StreamSelectorResult result = await customStreamSelector.SelectStreams( + channel, + nextPlayoutItem.Start, + audioVersion, + allSubtitles); + maybeAudioStream = result.AudioStream; + //maybeSubtitle = result.Subtitle; + } + + if (channel.StreamSelectorMode is ChannelStreamSelectorMode.Default || maybeAudioStream.IsNone) + { + maybeAudioStream = + await ffmpegStreamSelector.SelectAudioStream( + audioVersion, + channel.StreamingMode, + channel, + preferredAudioLanguage, + preferredAudioTitle, + shouldLogMessages: false, + cancellationToken); + + // maybeSubtitle = + // await ffmpegStreamSelector.SelectSubtitleStream( + // allSubtitles.ToImmutableList(), + // channel, + // preferredSubtitleLanguage, + // subtitleMode, + // cancellationToken); + } + + foreach (MediaStream audioStream in maybeAudioStream) + { + if (nextPlayoutItem.Tracks?.Audio?.StreamIndex is null) + { + nextPlayoutItem.Tracks ??= new Core.Next.PlayoutItemTracks(); + nextPlayoutItem.Tracks.Audio ??= new Core.Next.TrackSelection(); + nextPlayoutItem.Tracks.Audio.StreamIndex = audioStream.Index; + } + } + } + private async Task> SourceForItem( PlayoutItem playoutItem, CancellationToken cancellationToken) diff --git a/ErsatzTV.Core.Tests/FFmpeg/FFmpegStreamSelectorTests.cs b/ErsatzTV.Core.Tests/FFmpeg/FFmpegStreamSelectorTests.cs index 07a2cba0a..47b92b019 100644 --- a/ErsatzTV.Core.Tests/FFmpeg/FFmpegStreamSelectorTests.cs +++ b/ErsatzTV.Core.Tests/FFmpeg/FFmpegStreamSelectorTests.cs @@ -72,6 +72,7 @@ public class FFmpegStreamSelectorTests channel, "jpn", "Whatever", + shouldLogMessages: false, cancellationToken); selectedStream.IsSome.ShouldBeTrue(); foreach (MediaStream stream in selectedStream) @@ -134,6 +135,7 @@ public class FFmpegStreamSelectorTests channel, null, channel.PreferredAudioTitle, + shouldLogMessages: false, cancellationToken); selectedStream.IsSome.ShouldBeTrue(); foreach (MediaStream stream in selectedStream) diff --git a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs index 945a50b4e..1c3f67d95 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs @@ -153,6 +153,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService channel, preferredAudioLanguage, preferredAudioTitle, + shouldLogMessages: true, cancellationToken); maybeSubtitle = diff --git a/ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs b/ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs index 401650649..88d8d9166 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs @@ -47,21 +47,30 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector Channel channel, string preferredAudioLanguage, string preferredAudioTitle, + bool shouldLogMessages, CancellationToken cancellationToken) { if (streamingMode == StreamingMode.HttpLiveStreamingDirect && string.IsNullOrWhiteSpace(preferredAudioLanguage) && string.IsNullOrWhiteSpace(preferredAudioTitle)) { - _logger.LogDebug( - "Channel {Number} is HLS Direct with no preferred audio language or title; using all audio streams", - channel.Number); + if (shouldLogMessages) + { + _logger.LogDebug( + "Channel {Number} is HLS Direct with no preferred audio language or title; using all audio streams", + channel.Number); + } + return None; } string language = (preferredAudioLanguage ?? string.Empty).ToLowerInvariant(); if (string.IsNullOrWhiteSpace(language)) { - _logger.LogDebug("Channel {Number} has no preferred audio language code", channel.Number); + if (shouldLogMessages) + { + _logger.LogDebug("Channel {Number} has no preferred audio language code", channel.Number); + } + Option maybeDefaultLanguage = await _configElementRepository.GetValue( ConfigElementKey.FFmpegPreferredLanguageCode, cancellationToken); @@ -69,7 +78,11 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector lang => language = lang.ToLowerInvariant(), () => { - _logger.LogDebug("FFmpeg has no preferred audio language code; falling back to {Code}", "eng"); + if (shouldLogMessages) + { + _logger.LogDebug("FFmpeg has no preferred audio language code; falling back to {Code}", "eng"); + } + language = "eng"; }); } @@ -78,7 +91,10 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector GetTwoAndThreeLetterLanguageCodes(_languageCodeService.GetAllLanguageCodes([language])); if (allLanguageCodes.Count > 1) { - _logger.LogDebug("Preferred audio language has multiple codes {Codes}", allLanguageCodes); + if (shouldLogMessages) + { + _logger.LogDebug("Preferred audio language has multiple codes {Codes}", allLanguageCodes); + } } try @@ -93,7 +109,11 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector version.MediaItem.Id, version.MediaVersion); sw.Stop(); - _logger.LogDebug("SelectAudioStream duration: {Duration}", sw.Elapsed); + if (shouldLogMessages) + { + _logger.LogDebug("SelectAudioStream duration: {Duration}", sw.Elapsed); + } + if (result.IsSome) { return result; @@ -108,7 +128,11 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector version.MediaItem.Id, version.MediaVersion); sw2.Stop(); - _logger.LogDebug("SelectAudioStream duration: {Duration}", sw2.Elapsed); + if (shouldLogMessages) + { + _logger.LogDebug("SelectAudioStream duration: {Duration}", sw2.Elapsed); + } + if (result2.IsSome) { return result2; diff --git a/ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegStreamSelector.cs b/ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegStreamSelector.cs index e7aaae8d0..7ac1474dd 100644 --- a/ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegStreamSelector.cs +++ b/ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegStreamSelector.cs @@ -14,6 +14,7 @@ public interface IFFmpegStreamSelector Channel channel, string preferredAudioLanguage, string preferredAudioTitle, + bool shouldLogMessages, CancellationToken cancellationToken); Task> SelectSubtitleStream( diff --git a/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs b/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs index 3973776a2..1194dacba 100644 --- a/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs +++ b/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs @@ -1186,6 +1186,7 @@ public class TranscodingTests Channel channel, string preferredAudioLanguage, string preferredAudioTitle, + bool shouldLogMessages, CancellationToken cancellationToken) => Optional(version.MediaVersion.Streams.FirstOrDefault(s => s.MediaStreamKind == MediaStreamKind.Audio)) .AsTask();