From 79e8fa08775f0b5f94c574c294929d3d04029b84 Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:12:51 -0500 Subject: [PATCH] ignore specials when using season, episode order (#2399) --- CHANGELOG.md | 1 + .../Scheduling/SeasonEpisodeContentTests.cs | 30 +++++++++++++++++-- .../SeasonEpisodeMediaCollectionEnumerator.cs | 14 +++++++-- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad916f0b7..064370f44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Entrypoint is at `/app/scripted-schedules/entrypoint.py` - Scripts folder should be mounted to `/app/scripted-schedules/scripts` - Playouts should be created with scripted schedule `/app/scripted-schedules/entrypoint.py script-name` (no trailing `.py`) +- Automatically ignore Specials/Season 0 when using `Season, Episode` playback order ## [25.5.0] - 2025-09-01 ### Added diff --git a/ErsatzTV.Core.Tests/Scheduling/SeasonEpisodeContentTests.cs b/ErsatzTV.Core.Tests/Scheduling/SeasonEpisodeContentTests.cs index 99200d5d5..d00ad3dd1 100644 --- a/ErsatzTV.Core.Tests/Scheduling/SeasonEpisodeContentTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/SeasonEpisodeContentTests.cs @@ -73,17 +73,41 @@ public class SeasonEpisodeContentTests chronologicalContent.State.Seed.ShouldBe(0); } + [Test] + public void Episodes_Should_Ignore_Specials() + { + List contents = Episodes(10); + for (int i = 0; i < 2; i++) + { + ((Episode)contents[i]).Season = new Season { SeasonNumber = 0 }; + } + + var state = new CollectionEnumeratorState(); + + var chronologicalContent = new SeasonEpisodeMediaCollectionEnumerator(contents, state); + + for (var i = 0; i < 16; i++) + { + chronologicalContent.State.Index.ShouldBe(i % 8); + chronologicalContent.MoveNext(); + } + } + private static List Episodes(int count) => Range(1, count).Map(i => (MediaItem)new Episode { Id = i, - EpisodeMetadata = new List - { - new() + EpisodeMetadata = + [ + new EpisodeMetadata { ReleaseDate = new DateTime(2020, 1, 20 - i), EpisodeNumber = i } + ], + Season = new Season + { + SeasonNumber = 1 } }) .Reverse() diff --git a/ErsatzTV.Core/Scheduling/SeasonEpisodeMediaCollectionEnumerator.cs b/ErsatzTV.Core/Scheduling/SeasonEpisodeMediaCollectionEnumerator.cs index 8f32b20b7..7ae525b64 100644 --- a/ErsatzTV.Core/Scheduling/SeasonEpisodeMediaCollectionEnumerator.cs +++ b/ErsatzTV.Core/Scheduling/SeasonEpisodeMediaCollectionEnumerator.cs @@ -15,7 +15,9 @@ public sealed class SeasonEpisodeMediaCollectionEnumerator : IMediaCollectionEnu { CurrentIncludeInProgramGuide = Option.None; - _sortedMediaItems = mediaItems.OrderBy(identity, new SeasonEpisodeMediaComparer()).ToList(); + _sortedMediaItems = mediaItems + .Filter(mi => (mi is not Episode episode) || (episode.Season?.SeasonNumber ?? 0) > 0) + .OrderBy(identity, new SeasonEpisodeMediaComparer()).ToList(); _lazyMinimumDuration = new Lazy>(() => _sortedMediaItems.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone()); @@ -42,7 +44,15 @@ public sealed class SeasonEpisodeMediaCollectionEnumerator : IMediaCollectionEnu public Option Current => _sortedMediaItems.Count != 0 ? _sortedMediaItems[State.Index] : None; public Option CurrentIncludeInProgramGuide { get; } - public void MoveNext() => State.Index = (State.Index + 1) % _sortedMediaItems.Count; + public void MoveNext() + { + if (_sortedMediaItems.Count == 0) + { + return; + } + + State.Index = (State.Index + 1) % _sortedMediaItems.Count; + } public Option MinimumDuration => _lazyMinimumDuration.Value;