Browse Source

fix: subtitles with next engine

pull/2938/head
Jason Dove 1 month ago
parent
commit
1c2cab69b8
No known key found for this signature in database
  1. 3
      CHANGELOG.md
  2. 6
      ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandler.cs
  3. 136
      ErsatzTV.Core.Tests/FFmpeg/CustomStreamSelectorTests.cs
  4. 30
      ErsatzTV.Core/FFmpeg/CustomStreamSelector.cs
  5. 3
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  6. 3
      ErsatzTV.Core/Interfaces/FFmpeg/ICustomStreamSelector.cs
  7. 31
      ErsatzTV.Core/Next/Playout.cs
  8. 11
      ErsatzTV.Infrastructure/Scheduling/PlayoutItemConverter.cs

3
CHANGELOG.md

@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- A config file named `{channel_number}.json` (e.g. `1.json`) will apply to the channel with that number
- Channel overlays will override values from the default overlay which will override values from the FFmpeg Profile
- Add `epg_entries` support to image graphics elements
- Add (date-range based) alternate schedule system for sequential schedules
### Fixed
- Fix HLS Direct playback when JWT auth is also used
@ -26,6 +27,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -26,6 +27,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Use `und` language tag with sidecar subtitles that have no language in the file name
- Automatically remove already-played playout items from all on demand channels
- Automatically remove already-played playout items from (continuous) scripted schedules
- Extract embedded text subtitles (when enabled in settings) on channels that use custom stream selectors
- Fix subtitle playback using Next streaming engine
### Changed
- Upgrade Intel driver in docker containers to support latest Battlemage devices (e.g. B70)

6
ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandler.cs

@ -91,7 +91,8 @@ public class ExtractEmbeddedSubtitlesHandler : ExtractEmbeddedSubtitlesHandlerBa @@ -91,7 +91,8 @@ public class ExtractEmbeddedSubtitlesHandler : ExtractEmbeddedSubtitlesHandlerBa
.AsNoTracking()
.Filter(p => p.Channel.SubtitleMode != ChannelSubtitleMode.None ||
p.ProgramSchedule.Items.Any(psi =>
psi.SubtitleMode != null && psi.SubtitleMode != ChannelSubtitleMode.None))
psi.SubtitleMode != null && psi.SubtitleMode != ChannelSubtitleMode.None)
|| p.Channel.StreamSelectorMode == ChannelStreamSelectorMode.Custom)
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId.IfNone(-1), cancellationToken);
playoutIdsToCheck.AddRange(requestedPlayout.Map(p => p.Id));
@ -103,7 +104,8 @@ public class ExtractEmbeddedSubtitlesHandler : ExtractEmbeddedSubtitlesHandlerBa @@ -103,7 +104,8 @@ public class ExtractEmbeddedSubtitlesHandler : ExtractEmbeddedSubtitlesHandlerBa
.AsNoTracking()
.Filter(p => p.Channel.SubtitleMode != ChannelSubtitleMode.None ||
p.ProgramSchedule.Items.Any(psi =>
psi.SubtitleMode != null && psi.SubtitleMode != ChannelSubtitleMode.None))
psi.SubtitleMode != null && psi.SubtitleMode != ChannelSubtitleMode.None)
|| p.Channel.StreamSelectorMode == ChannelStreamSelectorMode.Custom)
.Map(p => p.Id)
.ToList();
}

136
ErsatzTV.Core.Tests/FFmpeg/CustomStreamSelectorTests.cs

@ -104,7 +104,8 @@ public class CustomStreamSelectorTests @@ -104,7 +104,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -134,7 +135,8 @@ public class CustomStreamSelectorTests @@ -134,7 +135,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -164,7 +166,8 @@ public class CustomStreamSelectorTests @@ -164,7 +166,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -200,7 +203,8 @@ public class CustomStreamSelectorTests @@ -200,7 +203,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -231,7 +235,8 @@ public class CustomStreamSelectorTests @@ -231,7 +235,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -263,7 +268,8 @@ public class CustomStreamSelectorTests @@ -263,7 +268,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -295,7 +301,8 @@ public class CustomStreamSelectorTests @@ -295,7 +301,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.Subtitle.IsSome.ShouldBeFalse();
}
@ -322,7 +329,8 @@ public class CustomStreamSelectorTests @@ -322,7 +329,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.Subtitle.IsSome.ShouldBeTrue();
@ -355,7 +363,8 @@ public class CustomStreamSelectorTests @@ -355,7 +363,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.Subtitle.IsSome.ShouldBeTrue();
@ -397,7 +406,8 @@ public class CustomStreamSelectorTests @@ -397,7 +406,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.Subtitle.IsSome.ShouldBeTrue();
@ -433,7 +443,8 @@ public class CustomStreamSelectorTests @@ -433,7 +443,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -471,7 +482,8 @@ public class CustomStreamSelectorTests @@ -471,7 +482,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -514,7 +526,8 @@ public class CustomStreamSelectorTests @@ -514,7 +526,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now.LocalDateTime.Date.AddHours(11).AddMinutes(59), // 11:59 AM
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -551,7 +564,8 @@ public class CustomStreamSelectorTests @@ -551,7 +564,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now.LocalDateTime.Date.AddHours(11).AddMinutes(59), // 11:59 AM
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -594,7 +608,12 @@ public class CustomStreamSelectorTests @@ -594,7 +608,12 @@ public class CustomStreamSelectorTests
var start = new DateTime(2026, 1, 11, 0, 0, 0, DateTimeKind.Unspecified); // sunday
var dto = new DateTimeOffset(start, tz.GetUtcOffset(start));
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, dto, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
dto,
_audioVersion,
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -631,7 +650,12 @@ public class CustomStreamSelectorTests @@ -631,7 +650,12 @@ public class CustomStreamSelectorTests
var start = new DateTime(2026, 1, 11, 0, 0, 0, DateTimeKind.Unspecified); // sunday
var dto = new DateTimeOffset(start, tz.GetUtcOffset(start));
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, dto, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
dto,
_audioVersion,
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -651,7 +675,8 @@ public class CustomStreamSelectorTests @@ -651,7 +675,8 @@ public class CustomStreamSelectorTests
}
[Test]
public async Task Should_Select_English_Audio_No_Subtitles_Day_Of_Week_Time_Of_Day_Content_Condition_Fail_Before()
public async Task
Should_Select_English_Audio_No_Subtitles_Day_Of_Week_Time_Of_Day_Content_Condition_Fail_Before()
{
// saturday from 9pm-11pm
const string YAML =
@ -675,7 +700,12 @@ public class CustomStreamSelectorTests @@ -675,7 +700,12 @@ public class CustomStreamSelectorTests
var start = new DateTime(2026, 1, 10, 20, 59, 59, DateTimeKind.Unspecified); // saturday at 8:59:59pm
var dto = new DateTimeOffset(start, tz.GetUtcOffset(start));
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, dto, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
dto,
_audioVersion,
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -689,7 +719,8 @@ public class CustomStreamSelectorTests @@ -689,7 +719,8 @@ public class CustomStreamSelectorTests
}
[Test]
public async Task Should_Select_English_Audio_No_Subtitles_Day_Of_Week_Time_Of_Day_Content_Condition_Fail_After()
public async Task
Should_Select_English_Audio_No_Subtitles_Day_Of_Week_Time_Of_Day_Content_Condition_Fail_After()
{
// saturday from 9pm-11pm
const string YAML =
@ -713,7 +744,12 @@ public class CustomStreamSelectorTests @@ -713,7 +744,12 @@ public class CustomStreamSelectorTests
var start = new DateTime(2026, 1, 10, 23, 0, 0, DateTimeKind.Unspecified); // saturday at 11:00pm
var dto = new DateTimeOffset(start, tz.GetUtcOffset(start));
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, dto, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
dto,
_audioVersion,
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -727,7 +763,8 @@ public class CustomStreamSelectorTests @@ -727,7 +763,8 @@ public class CustomStreamSelectorTests
}
[Test]
public async Task Should_Select_English_Audio_No_Subtitles_Day_Of_Week_Time_Of_Day_Content_Condition_Fail_Wrong_Day()
public async Task
Should_Select_English_Audio_No_Subtitles_Day_Of_Week_Time_Of_Day_Content_Condition_Fail_Wrong_Day()
{
// saturday from 9pm-11pm
const string YAML =
@ -751,7 +788,12 @@ public class CustomStreamSelectorTests @@ -751,7 +788,12 @@ public class CustomStreamSelectorTests
var start = new DateTime(2026, 1, 11, 22, 0, 0, DateTimeKind.Unspecified); // sunday at 10:00pm
var dto = new DateTimeOffset(start, tz.GetUtcOffset(start));
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, dto, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
dto,
_audioVersion,
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -789,7 +831,12 @@ public class CustomStreamSelectorTests @@ -789,7 +831,12 @@ public class CustomStreamSelectorTests
var start = new DateTime(2026, 1, 10, 22, 0, 0, DateTimeKind.Unspecified); // saturday at 10:00pm
var dto = new DateTimeOffset(start, tz.GetUtcOffset(start));
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, dto, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
dto,
_audioVersion,
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -810,7 +857,8 @@ public class CustomStreamSelectorTests @@ -810,7 +857,8 @@ public class CustomStreamSelectorTests
[Test]
[SetCulture("fr-FR")]
public async Task Should_Select_English_Audio_No_Subtitles_Day_Of_Week_Time_Of_Day_Content_Condition_Match_France()
public async Task
Should_Select_English_Audio_No_Subtitles_Day_Of_Week_Time_Of_Day_Content_Condition_Match_France()
{
// saturday from 9pm-11pm
const string YAML =
@ -834,7 +882,12 @@ public class CustomStreamSelectorTests @@ -834,7 +882,12 @@ public class CustomStreamSelectorTests
var start = new DateTime(2026, 1, 10, 22, 0, 0, DateTimeKind.Unspecified); // saturday at 10:00pm
var dto = new DateTimeOffset(start, tz.GetUtcOffset(start));
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, dto, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
dto,
_audioVersion,
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -875,7 +928,8 @@ public class CustomStreamSelectorTests @@ -875,7 +928,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -908,7 +962,8 @@ public class CustomStreamSelectorTests @@ -908,7 +962,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -943,7 +998,8 @@ public class CustomStreamSelectorTests @@ -943,7 +998,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.Subtitle.IsSome.ShouldBeTrue();
@ -978,7 +1034,8 @@ public class CustomStreamSelectorTests @@ -978,7 +1034,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.Subtitle.IsSome.ShouldBeTrue();
@ -1010,7 +1067,8 @@ public class CustomStreamSelectorTests @@ -1010,7 +1067,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.Subtitle.IsSome.ShouldBeTrue();
@ -1042,7 +1100,8 @@ public class CustomStreamSelectorTests @@ -1042,7 +1100,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.Subtitle.IsSome.ShouldBeTrue();
@ -1074,7 +1133,8 @@ public class CustomStreamSelectorTests @@ -1074,7 +1133,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -1106,7 +1166,8 @@ public class CustomStreamSelectorTests @@ -1106,7 +1166,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -1137,7 +1198,8 @@ public class CustomStreamSelectorTests @@ -1137,7 +1198,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeTrue();
@ -1169,7 +1231,8 @@ public class CustomStreamSelectorTests @@ -1169,7 +1231,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.Subtitle.IsSome.ShouldBeTrue();
@ -1202,7 +1265,8 @@ public class CustomStreamSelectorTests @@ -1202,7 +1265,8 @@ public class CustomStreamSelectorTests
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
_subtitles,
shouldLogMessages: true);
result.AudioStream.IsSome.ShouldBeFalse();
result.Subtitle.IsSome.ShouldBeFalse();

30
ErsatzTV.Core/FFmpeg/CustomStreamSelector.cs

@ -5,6 +5,7 @@ using ErsatzTV.Core.Domain; @@ -5,6 +5,7 @@ using ErsatzTV.Core.Domain;
using ErsatzTV.Core.FFmpeg.Selector;
using ErsatzTV.Core.Interfaces.FFmpeg;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using NCalc;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
@ -18,8 +19,11 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe @@ -18,8 +19,11 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe
Channel channel,
DateTimeOffset contentStartTime,
MediaItemAudioVersion audioVersion,
List<Subtitle> allSubtitles)
List<Subtitle> allSubtitles,
bool shouldLogMessages)
{
ILogger<CustomStreamSelector> log = shouldLogMessages ? logger : NullLogger<CustomStreamSelector>.Instance;
try
{
string streamSelectorFile = Path.Combine(
@ -28,11 +32,11 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe @@ -28,11 +32,11 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe
if (!fileSystem.File.Exists(streamSelectorFile))
{
logger.LogWarning("YAML stream selector file {File} does not exist; aborting.", channel.StreamSelector);
log.LogWarning("YAML stream selector file {File} does not exist; aborting.", channel.StreamSelector);
return StreamSelectorResult.None;
}
StreamSelector streamSelector = await LoadStreamSelector(streamSelectorFile);
StreamSelector streamSelector = await LoadStreamSelector(streamSelectorFile, log);
var audioStreams = audioVersion.MediaVersion.Streams
.Where(s => s.MediaStreamKind == MediaStreamKind.Audio)
@ -44,7 +48,7 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe @@ -44,7 +48,7 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe
{
if (!ContentMatchesCondition(channel, contentStartTime, streamSelectorItem.ContentCondition))
{
logger.LogDebug(
log.LogDebug(
"Content does not match selector item {@SelectorItem}",
streamSelectorItem);
continue;
@ -122,7 +126,7 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe @@ -122,7 +126,7 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe
{
candidateAudioStreams.Remove(audioStream);
logger.LogDebug(
log.LogDebug(
"Audio stream {@Stream} does not match selector item {@SelectorItem}",
new { Language = safeLanguage, Title = safeTitle },
streamSelectorItem);
@ -131,7 +135,7 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe @@ -131,7 +135,7 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe
{
passesAudio = true;
logger.LogDebug(
log.LogDebug(
"Audio stream {@Stream} matches selector item {@SelectorItem}",
new { Language = safeLanguage, Title = safeTitle },
streamSelectorItem);
@ -206,13 +210,15 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe @@ -206,13 +210,15 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe
}
}
// HLS Direct doesn't need to extract; Next streaming engine doesn't need to extract
if (channel.StreamingMode != StreamingMode.HttpLiveStreamingDirect &&
channel.StreamingEngine != StreamingEngine.Next &&
subtitle.SubtitleKind is SubtitleKind.Embedded && !subtitle.IsImage &&
!subtitle.IsExtracted)
{
candidateSubtitles.Remove(subtitle);
logger.LogDebug(
log.LogDebug(
"Subtitle {@Subtitle} is embedded text subtitle and NOT extracted; ignoring",
new { Language = safeLanguage, Title = safeTitle });
}
@ -220,7 +226,7 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe @@ -220,7 +226,7 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe
{
candidateSubtitles.Remove(subtitle);
logger.LogDebug(
log.LogDebug(
"Subtitle {@Subtitle} does not match selector item {@SelectorItem}",
new { Language = safeLanguage, Title = safeTitle },
streamSelectorItem);
@ -229,7 +235,7 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe @@ -229,7 +235,7 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe
{
passesSubtitles = true;
logger.LogDebug(
log.LogDebug(
"Subtitle {@Subtitle} matches selector item {@SelectorItem}",
new { Language = safeLanguage, Title = safeTitle },
streamSelectorItem);
@ -255,7 +261,7 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe @@ -255,7 +261,7 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe
}
catch (Exception ex)
{
logger.LogError(ex, "Unexpected error selecting streams");
log.LogError(ex, "Unexpected error selecting streams");
}
return StreamSelectorResult.None;
@ -325,7 +331,7 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe @@ -325,7 +331,7 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe
return expression.Evaluate() as bool? == true;
}
private async Task<StreamSelector> LoadStreamSelector(string streamSelectorFile)
private async Task<StreamSelector> LoadStreamSelector(string streamSelectorFile, ILogger<CustomStreamSelector> log)
{
try
{
@ -339,7 +345,7 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe @@ -339,7 +345,7 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe
}
catch (Exception ex)
{
logger.LogWarning(ex, "Error loading YAML stream selector");
log.LogWarning(ex, "Error loading YAML stream selector");
throw;
}
}

3
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -132,7 +132,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -132,7 +132,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
channel,
start,
audioVersion,
allSubtitles);
allSubtitles,
shouldLogMessages: true);
maybeAudioStream = result.AudioStream;
maybeSubtitle = result.Subtitle;

3
ErsatzTV.Core/Interfaces/FFmpeg/ICustomStreamSelector.cs

@ -9,5 +9,6 @@ public interface ICustomStreamSelector @@ -9,5 +9,6 @@ public interface ICustomStreamSelector
Channel channel,
DateTimeOffset contentStartTime,
MediaItemAudioVersion audioVersion,
List<Subtitle> allSubtitles);
List<Subtitle> allSubtitles,
bool shouldLogMessages);
}

31
ErsatzTV.Core/Next/Playout.cs

@ -248,7 +248,8 @@ namespace ErsatzTV.Core.Next @@ -248,7 +248,8 @@ namespace ErsatzTV.Core.Next
///
/// The hint fully replaces probing: values are not validated up front, so incorrect metadata
/// surfaces as an ffmpeg error during playback rather than a probe failure. Provide an entry
/// for every stream the pipeline needs to select — typically one video and one audio.
/// for every stream the pipeline needs to select — typically one video and one audio, plus
/// any subtitle stream a track selects.
/// </summary>
public partial class ProbeHint
{
@ -272,6 +273,14 @@ namespace ErsatzTV.Core.Next @@ -272,6 +273,14 @@ namespace ErsatzTV.Core.Next
[JsonProperty("format_name")]
public string FormatName { get; set; }
/// <summary>
/// Subtitle streams in the source. Omit (or use `[]`) for sources without subtitles;
/// defaults to empty. Provide an entry for any subtitle stream a track selects, otherwise
/// the requested subtitle stream cannot be located and subtitles are dropped.
/// </summary>
[JsonProperty("subtitle", NullValueHandling = NullValueHandling.Ignore)]
public List<SubtitleHint> Subtitle { get; set; }
/// <summary>
/// Video (and still-image) streams in the source. Omit (or use `[]`) for audio-only sources;
/// defaults to empty.
@ -304,6 +313,26 @@ namespace ErsatzTV.Core.Next @@ -304,6 +313,26 @@ namespace ErsatzTV.Core.Next
public long StreamIndex { get; set; }
}
/// <summary>
/// Probe metadata for a single subtitle stream.
/// </summary>
public partial class SubtitleHint
{
/// <summary>
/// Codec name as reported by ffprobe (e.g. "subrip", "ass", "hdmv_pgs_subtitle",
/// "dvd_subtitle"). Compared case-insensitively; image-based codecs are handled differently
/// from text-based ones.
/// </summary>
[JsonProperty("codec")]
public string Codec { get; set; }
/// <summary>
/// Zero-based index of this stream within the source.
/// </summary>
[JsonProperty("stream_index")]
public long StreamIndex { get; set; }
}
/// <summary>
/// Probe metadata for a single video (or still-image) stream. Optional fields left out
/// assume progressive, square-pixel, SDR content at 24 fps — the same fallbacks used when

11
ErsatzTV.Infrastructure/Scheduling/PlayoutItemConverter.cs

@ -150,11 +150,19 @@ public class PlayoutItemConverter( @@ -150,11 +150,19 @@ public class PlayoutItemConverter(
Codec = s.Codec,
Channels = s.Channels
}).ToList();
var sourceSubtitleHints = headVersion.Streams
.Where(s => s.MediaStreamKind is MediaStreamKind.Subtitle)
.Select(s => new Core.Next.SubtitleHint
{
StreamIndex = s.Index,
Codec = s.Codec
}).ToList();
nextPlayoutItem.Source.ProbeHint = new Core.Next.ProbeHint
{
Audio = sourceAudioHints,
Video = sourceVideoHints,
Subtitle = sourceSubtitleHints,
DurationMs = (long)headVersion.Duration.TotalMilliseconds
};
@ -378,7 +386,8 @@ public class PlayoutItemConverter( @@ -378,7 +386,8 @@ public class PlayoutItemConverter(
channel,
nextPlayoutItem.Start,
audioVersion,
allSubtitles);
allSubtitles,
shouldLogMessages: false);
maybeAudioStream = result.AudioStream;
maybeSubtitle = result.Subtitle;
}

Loading…
Cancel
Save