|
|
|
@ -107,7 +107,8 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector |
|
|
|
channel, |
|
|
|
channel, |
|
|
|
allLanguageCodes, |
|
|
|
allLanguageCodes, |
|
|
|
version.MediaItem.Id, |
|
|
|
version.MediaItem.Id, |
|
|
|
version.MediaVersion); |
|
|
|
version.MediaVersion, |
|
|
|
|
|
|
|
shouldLogMessages); |
|
|
|
sw.Stop(); |
|
|
|
sw.Stop(); |
|
|
|
if (shouldLogMessages) |
|
|
|
if (shouldLogMessages) |
|
|
|
{ |
|
|
|
{ |
|
|
|
@ -126,7 +127,8 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector |
|
|
|
channel, |
|
|
|
channel, |
|
|
|
allLanguageCodes, |
|
|
|
allLanguageCodes, |
|
|
|
version.MediaItem.Id, |
|
|
|
version.MediaItem.Id, |
|
|
|
version.MediaVersion); |
|
|
|
version.MediaVersion, |
|
|
|
|
|
|
|
shouldLogMessages); |
|
|
|
sw2.Stop(); |
|
|
|
sw2.Stop(); |
|
|
|
if (shouldLogMessages) |
|
|
|
if (shouldLogMessages) |
|
|
|
{ |
|
|
|
{ |
|
|
|
@ -143,11 +145,14 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
catch (Exception ex) |
|
|
|
catch (Exception ex) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (shouldLogMessages) |
|
|
|
{ |
|
|
|
{ |
|
|
|
_logger.LogError(ex, "Failed to execute audio stream selector script; falling back to built-in logic"); |
|
|
|
_logger.LogError(ex, "Failed to execute audio stream selector script; falling back to built-in logic"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return DefaultSelectAudioStream(version.MediaVersion, allLanguageCodes, preferredAudioTitle); |
|
|
|
return DefaultSelectAudioStream(version.MediaVersion, allLanguageCodes, preferredAudioTitle, shouldLogMessages); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public async Task<Option<Subtitle>> SelectSubtitleStream( |
|
|
|
public async Task<Option<Subtitle>> SelectSubtitleStream( |
|
|
|
@ -176,6 +181,9 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector |
|
|
|
|
|
|
|
|
|
|
|
var candidateSubtitles = subtitles.ToList(); |
|
|
|
var candidateSubtitles = subtitles.ToList(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// next engine doesn't need to specifically enable or pre-extract embedded subtitles
|
|
|
|
|
|
|
|
if (channel.StreamingEngine != StreamingEngine.Next) |
|
|
|
|
|
|
|
{ |
|
|
|
bool useEmbeddedSubtitles = await _configElementRepository |
|
|
|
bool useEmbeddedSubtitles = await _configElementRepository |
|
|
|
.GetValue<bool>(ConfigElementKey.FFmpegUseEmbeddedSubtitles, cancellationToken) |
|
|
|
.GetValue<bool>(ConfigElementKey.FFmpegUseEmbeddedSubtitles, cancellationToken) |
|
|
|
.IfNoneAsync(true); |
|
|
|
.IfNoneAsync(true); |
|
|
|
@ -187,7 +195,8 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector |
|
|
|
_logger.LogDebug("Ignoring embedded subtitles for channel {Number}", channel.Number); |
|
|
|
_logger.LogDebug("Ignoring embedded subtitles for channel {Number}", channel.Number); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
candidateSubtitles = candidateSubtitles.Filter(s => s.SubtitleKind is not SubtitleKind.Embedded).ToList(); |
|
|
|
candidateSubtitles = |
|
|
|
|
|
|
|
candidateSubtitles.Filter(s => s.SubtitleKind is not SubtitleKind.Embedded).ToList(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (channel.StreamingMode is not StreamingMode.HttpLiveStreamingDirect) |
|
|
|
if (channel.StreamingMode is not StreamingMode.HttpLiveStreamingDirect) |
|
|
|
@ -220,6 +229,7 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var allCodes = new List<string>(); |
|
|
|
var allCodes = new List<string>(); |
|
|
|
string language = (preferredSubtitleLanguage ?? string.Empty).ToLowerInvariant(); |
|
|
|
string language = (preferredSubtitleLanguage ?? string.Empty).ToLowerInvariant(); |
|
|
|
@ -294,7 +304,8 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector |
|
|
|
private Option<MediaStream> DefaultSelectAudioStream( |
|
|
|
private Option<MediaStream> DefaultSelectAudioStream( |
|
|
|
MediaVersion version, |
|
|
|
MediaVersion version, |
|
|
|
IReadOnlyCollection<string> preferredLanguageCodes, |
|
|
|
IReadOnlyCollection<string> preferredLanguageCodes, |
|
|
|
string preferredAudioTitle) |
|
|
|
string preferredAudioTitle, |
|
|
|
|
|
|
|
bool shouldLogMessages) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var audioStreams = version.Streams.Filter(s => s.MediaStreamKind == MediaStreamKind.Audio).ToList(); |
|
|
|
var audioStreams = version.Streams.Filter(s => s.MediaStreamKind == MediaStreamKind.Audio).ToList(); |
|
|
|
|
|
|
|
|
|
|
|
@ -303,27 +314,33 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector |
|
|
|
.ToList(); |
|
|
|
.ToList(); |
|
|
|
|
|
|
|
|
|
|
|
if (correctLanguage.Count != 0) |
|
|
|
if (correctLanguage.Count != 0) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (shouldLogMessages) |
|
|
|
{ |
|
|
|
{ |
|
|
|
_logger.LogDebug( |
|
|
|
_logger.LogDebug( |
|
|
|
"Found {Count} audio streams with preferred audio language code(s) {Code}", |
|
|
|
"Found {Count} audio streams with preferred audio language code(s) {Code}", |
|
|
|
correctLanguage.Count, |
|
|
|
correctLanguage.Count, |
|
|
|
preferredLanguageCodes); |
|
|
|
preferredLanguageCodes); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return PrioritizeAudioTitle(correctLanguage, preferredAudioTitle ?? string.Empty); |
|
|
|
return PrioritizeAudioTitle(correctLanguage, preferredAudioTitle ?? string.Empty, shouldLogMessages); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (shouldLogMessages) |
|
|
|
|
|
|
|
{ |
|
|
|
_logger.LogDebug( |
|
|
|
_logger.LogDebug( |
|
|
|
"Unable to find audio stream with preferred audio language code(s) {Code}", |
|
|
|
"Unable to find audio stream with preferred audio language code(s) {Code}", |
|
|
|
preferredLanguageCodes); |
|
|
|
preferredLanguageCodes); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return PrioritizeAudioTitle(audioStreams, preferredAudioTitle ?? string.Empty); |
|
|
|
return PrioritizeAudioTitle(audioStreams, preferredAudioTitle ?? string.Empty, shouldLogMessages); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private Option<MediaStream> PrioritizeAudioTitle(IReadOnlyCollection<MediaStream> streams, string title) |
|
|
|
private Option<MediaStream> PrioritizeAudioTitle(IReadOnlyCollection<MediaStream> streams, string title, bool shouldLogMessages) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (string.IsNullOrWhiteSpace(title)) |
|
|
|
if (string.IsNullOrWhiteSpace(title)) |
|
|
|
{ |
|
|
|
{ |
|
|
|
return PrioritizeDefault(streams); |
|
|
|
return PrioritizeDefault(streams, shouldLogMessages); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// prioritize matching titles
|
|
|
|
// prioritize matching titles
|
|
|
|
@ -331,31 +348,44 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector |
|
|
|
.Filter(ms => (ms.Title ?? string.Empty).Contains(title, StringComparison.OrdinalIgnoreCase)) |
|
|
|
.Filter(ms => (ms.Title ?? string.Empty).Contains(title, StringComparison.OrdinalIgnoreCase)) |
|
|
|
.ToList(); |
|
|
|
.ToList(); |
|
|
|
if (matchingTitle.Count != 0) |
|
|
|
if (matchingTitle.Count != 0) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (shouldLogMessages) |
|
|
|
{ |
|
|
|
{ |
|
|
|
_logger.LogDebug( |
|
|
|
_logger.LogDebug( |
|
|
|
"Found {Count} audio streams with preferred title {Title}", |
|
|
|
"Found {Count} audio streams with preferred title {Title}", |
|
|
|
matchingTitle.Count, |
|
|
|
matchingTitle.Count, |
|
|
|
title); |
|
|
|
title); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return PrioritizeDefault(matchingTitle); |
|
|
|
return PrioritizeDefault(matchingTitle, shouldLogMessages); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (shouldLogMessages) |
|
|
|
|
|
|
|
{ |
|
|
|
_logger.LogDebug("Unable to find audio stream with preferred title {Title}", title); |
|
|
|
_logger.LogDebug("Unable to find audio stream with preferred title {Title}", title); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return PrioritizeDefault(streams); |
|
|
|
return PrioritizeDefault(streams, shouldLogMessages); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private Option<MediaStream> PrioritizeDefault(IReadOnlyCollection<MediaStream> streams) |
|
|
|
private Option<MediaStream> PrioritizeDefault(IReadOnlyCollection<MediaStream> streams, bool shouldLogMessages) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var sorted = streams.OrderByDescending(s => s.Channels).ToList(); |
|
|
|
var sorted = streams.OrderByDescending(s => s.Channels).ToList(); |
|
|
|
Option<MediaStream> maybeDefault = Optional(sorted.Find(s => s.Default)); |
|
|
|
Option<MediaStream> maybeDefault = Optional(sorted.Find(s => s.Default)); |
|
|
|
foreach (MediaStream stream in maybeDefault) |
|
|
|
foreach (MediaStream stream in maybeDefault) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (shouldLogMessages) |
|
|
|
{ |
|
|
|
{ |
|
|
|
_logger.LogDebug("Found audio stream flagged as default"); |
|
|
|
_logger.LogDebug("Found audio stream flagged as default"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return stream; |
|
|
|
return stream; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (shouldLogMessages) |
|
|
|
|
|
|
|
{ |
|
|
|
_logger.LogDebug("Unable to find default audio stream; selecting stream with most channels"); |
|
|
|
_logger.LogDebug("Unable to find default audio stream; selecting stream with most channels"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return streams.HeadOrNone(); |
|
|
|
return streams.HeadOrNone(); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -364,20 +394,33 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector |
|
|
|
Channel channel, |
|
|
|
Channel channel, |
|
|
|
List<string> preferredLanguageCodes, |
|
|
|
List<string> preferredLanguageCodes, |
|
|
|
int episodeId, |
|
|
|
int episodeId, |
|
|
|
MediaVersion version) |
|
|
|
MediaVersion version, |
|
|
|
|
|
|
|
bool shouldLogMessages) |
|
|
|
{ |
|
|
|
{ |
|
|
|
string jsScriptPath = Path.ChangeExtension( |
|
|
|
string jsScriptPath = Path.ChangeExtension( |
|
|
|
Path.Combine(FileSystemLayout.AudioStreamSelectorScriptsFolder, "episode"), |
|
|
|
Path.Combine(FileSystemLayout.AudioStreamSelectorScriptsFolder, "episode"), |
|
|
|
"js"); |
|
|
|
"js"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (shouldLogMessages) |
|
|
|
|
|
|
|
{ |
|
|
|
_logger.LogDebug("Checking for JS Script at {Path}", jsScriptPath); |
|
|
|
_logger.LogDebug("Checking for JS Script at {Path}", jsScriptPath); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (!_fileSystem.File.Exists(jsScriptPath)) |
|
|
|
if (!_fileSystem.File.Exists(jsScriptPath)) |
|
|
|
{ |
|
|
|
{ |
|
|
|
_logger.LogDebug("Unable to locate episode audio stream selector script; falling back to built-in logic"); |
|
|
|
if (shouldLogMessages) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
_logger.LogDebug( |
|
|
|
|
|
|
|
"Unable to locate episode audio stream selector script; falling back to built-in logic"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return Option<MediaStream>.None; |
|
|
|
return Option<MediaStream>.None; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (shouldLogMessages) |
|
|
|
|
|
|
|
{ |
|
|
|
_logger.LogDebug("Found JS Script at {Path}", jsScriptPath); |
|
|
|
_logger.LogDebug("Found JS Script at {Path}", jsScriptPath); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
await _scriptEngine.LoadAsync(jsScriptPath); |
|
|
|
await _scriptEngine.LoadAsync(jsScriptPath); |
|
|
|
|
|
|
|
|
|
|
|
@ -397,28 +440,40 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector |
|
|
|
preferredLanguageCodes.ToArray(), |
|
|
|
preferredLanguageCodes.ToArray(), |
|
|
|
audioStreams); |
|
|
|
audioStreams); |
|
|
|
|
|
|
|
|
|
|
|
return ProcessScriptResult(version, result); |
|
|
|
return ProcessScriptResult(version, result, shouldLogMessages); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private async Task<Option<MediaStream>> SelectMovieAudioStream( |
|
|
|
private async Task<Option<MediaStream>> SelectMovieAudioStream( |
|
|
|
Channel channel, |
|
|
|
Channel channel, |
|
|
|
List<string> preferredLanguageCodes, |
|
|
|
List<string> preferredLanguageCodes, |
|
|
|
int movieId, |
|
|
|
int movieId, |
|
|
|
MediaVersion version) |
|
|
|
MediaVersion version, |
|
|
|
|
|
|
|
bool shouldLogMessages) |
|
|
|
{ |
|
|
|
{ |
|
|
|
string jsScriptPath = Path.ChangeExtension( |
|
|
|
string jsScriptPath = Path.ChangeExtension( |
|
|
|
Path.Combine(FileSystemLayout.AudioStreamSelectorScriptsFolder, "movie"), |
|
|
|
Path.Combine(FileSystemLayout.AudioStreamSelectorScriptsFolder, "movie"), |
|
|
|
"js"); |
|
|
|
"js"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (shouldLogMessages) |
|
|
|
|
|
|
|
{ |
|
|
|
_logger.LogDebug("Checking for JS Script at {Path}", jsScriptPath); |
|
|
|
_logger.LogDebug("Checking for JS Script at {Path}", jsScriptPath); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (!_fileSystem.File.Exists(jsScriptPath)) |
|
|
|
if (!_fileSystem.File.Exists(jsScriptPath)) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (shouldLogMessages) |
|
|
|
{ |
|
|
|
{ |
|
|
|
_logger.LogDebug( |
|
|
|
_logger.LogDebug( |
|
|
|
"Unable to locate movie audio stream selector script; falling back to built-in logic"); |
|
|
|
"Unable to locate movie audio stream selector script; falling back to built-in logic"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return Option<MediaStream>.None; |
|
|
|
return Option<MediaStream>.None; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (shouldLogMessages) |
|
|
|
|
|
|
|
{ |
|
|
|
_logger.LogDebug("Found JS Script at {Path}", jsScriptPath); |
|
|
|
_logger.LogDebug("Found JS Script at {Path}", jsScriptPath); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
await _scriptEngine.LoadAsync(jsScriptPath); |
|
|
|
await _scriptEngine.LoadAsync(jsScriptPath); |
|
|
|
|
|
|
|
|
|
|
|
@ -435,34 +490,44 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector |
|
|
|
preferredLanguageCodes.ToArray(), |
|
|
|
preferredLanguageCodes.ToArray(), |
|
|
|
audioStreams); |
|
|
|
audioStreams); |
|
|
|
|
|
|
|
|
|
|
|
return ProcessScriptResult(version, result); |
|
|
|
return ProcessScriptResult(version, result, shouldLogMessages); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private Option<MediaStream> ProcessScriptResult(MediaVersion version, object result) |
|
|
|
private Option<MediaStream> ProcessScriptResult(MediaVersion version, object result, bool shouldLogMessages) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (result is double d) |
|
|
|
if (result is double d) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var streamIndex = (int)d; |
|
|
|
var streamIndex = (int)d; |
|
|
|
Option<MediaStream> maybeStream = version.Streams.Find(s => s.Index == streamIndex); |
|
|
|
Option<MediaStream> maybeStream = version.Streams.Find(s => s.Index == streamIndex); |
|
|
|
foreach (MediaStream stream in maybeStream) |
|
|
|
foreach (MediaStream stream in maybeStream) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (shouldLogMessages) |
|
|
|
{ |
|
|
|
{ |
|
|
|
_logger.LogDebug( |
|
|
|
_logger.LogDebug( |
|
|
|
"JS Script returned audio stream index {Index} with language {Language} and {Channels} audio channel(s)", |
|
|
|
"JS Script returned audio stream index {Index} with language {Language} and {Channels} audio channel(s)", |
|
|
|
streamIndex, |
|
|
|
streamIndex, |
|
|
|
stream.Language, |
|
|
|
stream.Language, |
|
|
|
stream.Channels); |
|
|
|
stream.Channels); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return stream; |
|
|
|
return stream; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (shouldLogMessages) |
|
|
|
|
|
|
|
{ |
|
|
|
_logger.LogWarning( |
|
|
|
_logger.LogWarning( |
|
|
|
"JS Script returned audio stream index {Index} which does not exist", |
|
|
|
"JS Script returned audio stream index {Index} which does not exist", |
|
|
|
streamIndex); |
|
|
|
streamIndex); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
else |
|
|
|
else |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (shouldLogMessages) |
|
|
|
{ |
|
|
|
{ |
|
|
|
_logger.LogInformation( |
|
|
|
_logger.LogInformation( |
|
|
|
"JS Script did not return an audio stream index; falling back to built-in logic"); |
|
|
|
"JS Script did not return an audio stream index; falling back to built-in logic"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return Option<MediaStream>.None; |
|
|
|
return Option<MediaStream>.None; |
|
|
|
} |
|
|
|
} |
|
|
|
|