Browse Source

add day_of_week to channel stream selector content_condition (#2766)

pull/2767/head
Jason Dove 7 months ago committed by GitHub
parent
commit
b72d150775
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      CHANGELOG.md
  2. 284
      ErsatzTV.Core.Tests/FFmpeg/CustomStreamSelectorTests.cs
  3. 9
      ErsatzTV.Core/FFmpeg/CustomStreamSelector.cs

7
CHANGELOG.md

@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Added
- Channel stream selector: add zero-based culture-specific `day_of_week` to `content_condition`, for example:
- en-US can match sunday using `day_of_week = 0`
- fr-FR can match sunday using `day_of_week = 6`
- As a complete example, to match Saturday from 9pm (inclusive) to 11pm (exclusive), based on content start time
- `content_condition: day_of_week = 6 and (time_of_day_seconds >= 75600 and time_of_day_seconds < 82800)`
### Fixed ### Fixed
- Use code signing on all Windows executables (`ErsatzTV-Windows.exe`, `ErsatzTV.exe`, `ErsatzTV.Scanner.exe`) - Use code signing on all Windows executables (`ErsatzTV-Windows.exe`, `ErsatzTV.exe`, `ErsatzTV.Scanner.exe`)
- Respect `z_index` (draw order) on all graphics element types - Respect `z_index` (draw order) on all graphics element types

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

@ -7,6 +7,7 @@ using NUnit.Framework;
using Serilog; using Serilog;
using Shouldly; using Shouldly;
using Testably.Abstractions.Testing; using Testably.Abstractions.Testing;
using TimeZoneConverter;
namespace ErsatzTV.Core.Tests.FFmpeg; namespace ErsatzTV.Core.Tests.FFmpeg;
@ -569,6 +570,289 @@ public class CustomStreamSelectorTests
} }
} }
[Test]
public async Task Should_Select_English_Audio_No_Subtitles_Day_Of_Week_Content_Condition_Fail()
{
const string YAML =
"""
---
items:
- audio_language: ["ja"]
subtitle_language: ["eng"]
content_condition: "day_of_week = 1"
- audio_language: ["eng"]
disable_subtitles: true
""";
var fileSystem = new MockFileSystem();
fileSystem.Initialize()
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
var tz = TZConvert.GetTimeZoneInfo("America/Chicago");
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);
result.AudioStream.IsSome.ShouldBeTrue();
foreach (MediaStream audioStream in result.AudioStream)
{
audioStream.Index.ShouldBe(1);
audioStream.Language.ShouldBe("eng");
}
result.Subtitle.IsSome.ShouldBeFalse();
}
[Test]
public async Task Should_Select_English_Audio_No_Subtitles_Day_Of_Week_Content_Condition_Match()
{
const string YAML =
"""
---
items:
- audio_language: ["ja"]
subtitle_language: ["eng"]
content_condition: "day_of_week = 0"
- audio_language: ["eng"]
disable_subtitles: true
""";
var fileSystem = new MockFileSystem();
fileSystem.Initialize()
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
var tz = TZConvert.GetTimeZoneInfo("America/Chicago");
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);
result.AudioStream.IsSome.ShouldBeTrue();
foreach (MediaStream audioStream in result.AudioStream)
{
audioStream.Index.ShouldBe(0);
audioStream.Language.ShouldBe("ja");
}
result.Subtitle.IsSome.ShouldBeTrue();
foreach (Subtitle subtitle in result.Subtitle)
{
subtitle.Id.ShouldBe(2);
subtitle.Language.ShouldBe("eng");
}
}
[Test]
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 =
"""
---
items:
- audio_language: ["ja"]
subtitle_language: ["eng"]
content_condition: "day_of_week = 6 and (time_of_day_seconds >= 75600 and time_of_day_seconds < 82800)"
- audio_language: ["eng"]
disable_subtitles: true
""";
var fileSystem = new MockFileSystem();
fileSystem.Initialize()
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
var tz = TZConvert.GetTimeZoneInfo("America/Chicago");
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);
result.AudioStream.IsSome.ShouldBeTrue();
foreach (MediaStream audioStream in result.AudioStream)
{
audioStream.Index.ShouldBe(1);
audioStream.Language.ShouldBe("eng");
}
result.Subtitle.IsSome.ShouldBeFalse();
}
[Test]
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 =
"""
---
items:
- audio_language: ["ja"]
subtitle_language: ["eng"]
content_condition: "day_of_week = 6 and (time_of_day_seconds >= 75600 and time_of_day_seconds < 82800)"
- audio_language: ["eng"]
disable_subtitles: true
""";
var fileSystem = new MockFileSystem();
fileSystem.Initialize()
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
var tz = TZConvert.GetTimeZoneInfo("America/Chicago");
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);
result.AudioStream.IsSome.ShouldBeTrue();
foreach (MediaStream audioStream in result.AudioStream)
{
audioStream.Index.ShouldBe(1);
audioStream.Language.ShouldBe("eng");
}
result.Subtitle.IsSome.ShouldBeFalse();
}
[Test]
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 =
"""
---
items:
- audio_language: ["ja"]
subtitle_language: ["eng"]
content_condition: "day_of_week = 6 and (time_of_day_seconds >= 75600 and time_of_day_seconds < 82800)"
- audio_language: ["eng"]
disable_subtitles: true
""";
var fileSystem = new MockFileSystem();
fileSystem.Initialize()
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
var tz = TZConvert.GetTimeZoneInfo("America/Chicago");
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);
result.AudioStream.IsSome.ShouldBeTrue();
foreach (MediaStream audioStream in result.AudioStream)
{
audioStream.Index.ShouldBe(1);
audioStream.Language.ShouldBe("eng");
}
result.Subtitle.IsSome.ShouldBeFalse();
}
[Test]
public async Task Should_Select_English_Audio_No_Subtitles_Day_Of_Week_Time_Of_Day_Content_Condition_Match()
{
// saturday from 9pm-11pm
const string YAML =
"""
---
items:
- audio_language: ["ja"]
subtitle_language: ["eng"]
content_condition: "day_of_week = 6 and (time_of_day_seconds >= 75600 and time_of_day_seconds < 82800)"
- audio_language: ["eng"]
disable_subtitles: true
""";
var fileSystem = new MockFileSystem();
fileSystem.Initialize()
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
var tz = TZConvert.GetTimeZoneInfo("America/Chicago");
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);
result.AudioStream.IsSome.ShouldBeTrue();
foreach (MediaStream audioStream in result.AudioStream)
{
audioStream.Index.ShouldBe(0);
audioStream.Language.ShouldBe("ja");
}
result.Subtitle.IsSome.ShouldBeTrue();
foreach (Subtitle subtitle in result.Subtitle)
{
subtitle.Id.ShouldBe(2);
subtitle.Language.ShouldBe("eng");
}
}
[Test]
[SetCulture("fr-FR")]
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 =
"""
---
items:
- audio_language: ["ja"]
subtitle_language: ["eng"]
content_condition: "day_of_week = 5 and (time_of_day_seconds >= 75600 and time_of_day_seconds < 82800)"
- audio_language: ["eng"]
disable_subtitles: true
""";
var fileSystem = new MockFileSystem();
fileSystem.Initialize()
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
var tz = TZConvert.GetTimeZoneInfo("America/Chicago");
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);
result.AudioStream.IsSome.ShouldBeTrue();
foreach (MediaStream audioStream in result.AudioStream)
{
audioStream.Index.ShouldBe(0);
audioStream.Language.ShouldBe("ja");
}
result.Subtitle.IsSome.ShouldBeTrue();
foreach (Subtitle subtitle in result.Subtitle)
{
subtitle.Id.ShouldBe(2);
subtitle.Language.ShouldBe("eng");
}
}
[Test] [Test]
public async Task Should_Ignore_Blocked_Audio_Title() public async Task Should_Ignore_Blocked_Audio_Title()
{ {

9
ErsatzTV.Core/FFmpeg/CustomStreamSelector.cs

@ -1,3 +1,4 @@
using System.Globalization;
using System.IO.Abstractions; using System.IO.Abstractions;
using System.IO.Enumeration; using System.IO.Enumeration;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
@ -316,6 +317,7 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe
"channel_number" => channel.Number, "channel_number" => channel.Number,
"channel_name" => channel.Name, "channel_name" => channel.Name,
"time_of_day_seconds" => contentStartTime.LocalDateTime.TimeOfDay.TotalSeconds, "time_of_day_seconds" => contentStartTime.LocalDateTime.TimeOfDay.TotalSeconds,
"day_of_week" => GetLocalizedDayOfWeekIndex(contentStartTime),
_ => e.Result _ => e.Result
}; };
}; };
@ -341,4 +343,11 @@ public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSe
throw; throw;
} }
} }
private static int GetLocalizedDayOfWeekIndex(DateTimeOffset date)
{
DayOfWeek first = CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
DayOfWeek current = date.DayOfWeek;
return ((int)current - (int)first + 7) % 7;
}
} }

Loading…
Cancel
Save