diff --git a/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs b/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs index 27da5c74c..89ae37382 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs @@ -68,12 +68,12 @@ namespace ErsatzTV.Core.FFmpeg return this; } - public Option Build(int videoStreamIndex, int audioStreamIndex) + public Option Build(int videoStreamIndex, Option audioStreamIndex) { var complexFilter = new StringBuilder(); var videoLabel = $"0:{videoStreamIndex}"; - var audioLabel = $"0:{audioStreamIndex}"; + string audioLabel = audioStreamIndex.Match(index => $"0:{index}", () => "0:a"); HardwareAccelerationKind acceleration = _hardwareAccelerationKind.IfNone(HardwareAccelerationKind.None); bool isHardwareDecode = acceleration switch diff --git a/ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs b/ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs index 533bd0e79..230186b20 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs @@ -22,6 +22,7 @@ using System; using System.Collections.Generic; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.FFmpeg; +using LanguageExt; using static LanguageExt.Prelude; namespace ErsatzTV.Core.FFmpeg @@ -46,7 +47,7 @@ namespace ErsatzTV.Core.FFmpeg FFmpegProfile ffmpegProfile, MediaVersion version, MediaStream videoStream, - MediaStream audioStream, + Option audioStream, DateTimeOffset start, DateTimeOffset now) { @@ -113,10 +114,14 @@ namespace ErsatzTV.Core.FFmpeg result.AudioBitrate = ffmpegProfile.AudioBitrate; result.AudioBufferSize = ffmpegProfile.AudioBufferSize; - if (audioStream.Channels != ffmpegProfile.AudioChannels) - { - result.AudioChannels = ffmpegProfile.AudioChannels; - } + audioStream.IfSome( + stream => + { + if (stream.Channels != ffmpegProfile.AudioChannels) + { + result.AudioChannels = ffmpegProfile.AudioChannels; + } + }); result.AudioSampleRate = ffmpegProfile.AudioSampleRate; result.AudioDuration = version.Duration; diff --git a/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs b/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs index f6daf5ec8..629c1163f 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs @@ -354,12 +354,15 @@ namespace ErsatzTV.Core.FFmpeg return this; } - public FFmpegProcessBuilder WithFilterComplex(int videoStreamIndex, int audioStreamIndex) + public FFmpegProcessBuilder WithFilterComplex(MediaStream videoStream, Option maybeAudioStream) { + int videoStreamIndex = videoStream.Index; + Option maybeIndex = maybeAudioStream.Map(ms => ms.Index); + var videoLabel = $"0:{videoStreamIndex}"; - var audioLabel = $"0:{audioStreamIndex}"; + var audioLabel = $"0:{maybeIndex.Match(i => i.ToString(), () => "a")}"; - Option maybeFilter = _complexFilterBuilder.Build(videoStreamIndex, audioStreamIndex); + Option maybeFilter = _complexFilterBuilder.Build(videoStreamIndex, maybeIndex); maybeFilter.IfSome( filter => { diff --git a/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs b/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs index 46b4d5bd6..d6cc2d8e2 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs @@ -30,14 +30,14 @@ namespace ErsatzTV.Core.FFmpeg DateTimeOffset now) { MediaStream videoStream = await _ffmpegStreamSelector.SelectVideoStream(channel, version); - MediaStream audioStream = await _ffmpegStreamSelector.SelectAudioStream(channel, version); + Option maybeAudioStream = await _ffmpegStreamSelector.SelectAudioStream(channel, version); FFmpegPlaybackSettings playbackSettings = _playbackSettingsCalculator.CalculateSettings( channel.StreamingMode, channel.FFmpegProfile, version, videoStream, - audioStream, + maybeAudioStream, start, now); @@ -67,7 +67,7 @@ namespace ErsatzTV.Core.FFmpeg } builder = builder - .WithFilterComplex(videoStream.Index, audioStream.Index); + .WithFilterComplex(videoStream, maybeAudioStream); }, () => { @@ -76,18 +76,18 @@ namespace ErsatzTV.Core.FFmpeg builder = builder .WithDeinterlace(playbackSettings.Deinterlace) .WithBlackBars(channel.FFmpegProfile.Resolution) - .WithFilterComplex(videoStream.Index, audioStream.Index); + .WithFilterComplex(videoStream, maybeAudioStream); } else if (playbackSettings.Deinterlace) { builder = builder.WithDeinterlace(playbackSettings.Deinterlace) .WithAlignedAudio(playbackSettings.AudioDuration) - .WithFilterComplex(videoStream.Index, audioStream.Index); + .WithFilterComplex(videoStream, maybeAudioStream); } else { builder = builder - .WithFilterComplex(videoStream.Index, audioStream.Index); + .WithFilterComplex(videoStream, maybeAudioStream); } }); diff --git a/ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs b/ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs index 8e680ffac..3889038cc 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs @@ -6,6 +6,7 @@ using ErsatzTV.Core.Interfaces.FFmpeg; using ErsatzTV.Core.Interfaces.Repositories; using LanguageExt; using Microsoft.Extensions.Logging; +using static LanguageExt.Prelude; namespace ErsatzTV.Core.FFmpeg { @@ -25,8 +26,17 @@ namespace ErsatzTV.Core.FFmpeg public Task SelectVideoStream(Channel channel, MediaVersion version) => version.Streams.First(s => s.MediaStreamKind == MediaStreamKind.Video).AsTask(); - public async Task SelectAudioStream(Channel channel, MediaVersion version) + public async Task> SelectAudioStream(Channel channel, MediaVersion version) { + if (channel.StreamingMode == StreamingMode.HttpLiveStreaming && + string.IsNullOrWhiteSpace(channel.PreferredLanguageCode)) + { + _logger.LogDebug( + "Channel {Number} is HLS with no preferred language; using all audio streams", + channel.Number); + return None; + } + var audioStreams = version.Streams.Filter(s => s.MediaStreamKind == MediaStreamKind.Audio).ToList(); string language = (channel.PreferredLanguageCode ?? string.Empty).ToLowerInvariant(); diff --git a/ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegStreamSelector.cs b/ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegStreamSelector.cs index d18b9268d..0ca32f8b6 100644 --- a/ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegStreamSelector.cs +++ b/ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegStreamSelector.cs @@ -1,11 +1,12 @@ using System.Threading.Tasks; using ErsatzTV.Core.Domain; +using LanguageExt; namespace ErsatzTV.Core.Interfaces.FFmpeg { public interface IFFmpegStreamSelector { Task SelectVideoStream(Channel channel, MediaVersion version); - Task SelectAudioStream(Channel channel, MediaVersion version); + Task> SelectAudioStream(Channel channel, MediaVersion version); } }