diff --git a/ErsatzTV.Application/MediaItems/Queries/GetAggregateMediaItemsHandler.cs b/ErsatzTV.Application/MediaItems/Queries/GetAggregateMediaItemsHandler.cs index 46abd2a92..189916267 100644 --- a/ErsatzTV.Application/MediaItems/Queries/GetAggregateMediaItemsHandler.cs +++ b/ErsatzTV.Application/MediaItems/Queries/GetAggregateMediaItemsHandler.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -25,7 +26,7 @@ namespace ErsatzTV.Application.MediaItems.Queries if (!string.IsNullOrEmpty(request.SearchString)) { allItems = allItems.Filter( - i => i.Metadata?.Title.ToLowerInvariant().Contains(request.SearchString.ToLowerInvariant()) == + i => i.Metadata?.Title.Contains(request.SearchString, StringComparison.OrdinalIgnoreCase) == true); } diff --git a/ErsatzTV.Core.Tests/FFmpeg/FFmpegPlaybackSettingsServiceTests.cs b/ErsatzTV.Core.Tests/FFmpeg/FFmpegPlaybackSettingsServiceTests.cs index 85ae5fd1e..6497ebb1e 100644 --- a/ErsatzTV.Core.Tests/FFmpeg/FFmpegPlaybackSettingsServiceTests.cs +++ b/ErsatzTV.Core.Tests/FFmpeg/FFmpegPlaybackSettingsServiceTests.cs @@ -6,6 +6,7 @@ using NUnit.Framework; namespace ErsatzTV.Core.Tests.FFmpeg { + [TestFixture] public class FFmpegPlaybackSettingsCalculatorTests { public class CalculateSettings diff --git a/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs b/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs new file mode 100644 index 000000000..2e2500abc --- /dev/null +++ b/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs @@ -0,0 +1,40 @@ +using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Metadata; +using FluentAssertions; +using NUnit.Framework; + +namespace ErsatzTV.Core.Tests.Metadata +{ + [TestFixture] + public class FallbackMetadataProviderTests + { + [Test] + [TestCase("Awesome Show - s01e02.mkv", "Awesome Show", 1, 2)] + [TestCase("Awesome Show - S01E02.mkv", "Awesome Show", 1, 2)] + [TestCase("Awesome Show - s1e2.mkv", "Awesome Show", 1, 2)] + [TestCase("Awesome Show - S1E2.mkv", "Awesome Show", 1, 2)] + [TestCase("Awesome Show - s01e02 - Episode Title.mkv", "Awesome Show", 1, 2)] + [TestCase("Awesome Show - S01E02 - Episode Title.mkv", "Awesome Show", 1, 2)] + [TestCase("Awesome Show - s1e2 - Episode Title.mkv", "Awesome Show", 1, 2)] + [TestCase("Awesome Show - S1E2 - Episode Title.mkv", "Awesome Show", 1, 2)] + [TestCase("Awesome Show (2021) - s01e02 - Episode Title.mkv", "Awesome Show (2021)", 1, 2)] + [TestCase("Awesome Show (2021) - S01E02 - Episode Title.mkv", "Awesome Show (2021)", 1, 2)] + [TestCase("Awesome Show (2021) - s1e2 - Episode Title.mkv", "Awesome Show (2021)", 1, 2)] + [TestCase("Awesome Show (2021) - S1E2 - Episode Title.mkv", "Awesome Show (2021)", 1, 2)] + [TestCase("Awesome Show - s01e02 - Episode Title-720p.mkv", "Awesome Show", 1, 2)] + [TestCase("Awesome Show - S01E02 - Episode Title-720p.mkv", "Awesome Show", 1, 2)] + [TestCase("Awesome Show - s1e2 - Episode Title-720p.mkv", "Awesome Show", 1, 2)] + [TestCase("Awesome Show - S1E2 - Episode Title-720p.mkv", "Awesome Show", 1, 2)] + [TestCase("Awesome Show (2021) - S01E02 - Description; More Description (1080p QUALITY codec GROUP).mkv", "Awesome Show (2021)", 1, 2)] + [TestCase("Awesome.Show.S01E02.Description.more.Description.QUAlity.codec.CODEC-GROUP.mkv", "Awesome.Show", 1, 2)] + public void GetFallbackMetadata_ShouldHandleVariousFormats(string path, string title, int season, int episode) + { + var metadata = FallbackMetadataProvider.GetFallbackMetadata(path); + + metadata.MediaType.Should().Be(MediaType.TvShow); + metadata.Title.Should().Be(title); + metadata.SeasonNumber.Should().Be(season); + metadata.EpisodeNumber.Should().Be(episode); + } + } +} diff --git a/ErsatzTV.Core.Tests/Scheduling/ChronologicalContentTests.cs b/ErsatzTV.Core.Tests/Scheduling/ChronologicalContentTests.cs index 6473f6264..9a9e35b74 100644 --- a/ErsatzTV.Core.Tests/Scheduling/ChronologicalContentTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/ChronologicalContentTests.cs @@ -9,6 +9,7 @@ using static LanguageExt.Prelude; namespace ErsatzTV.Core.Tests.Scheduling { + [TestFixture] public class ChronologicalContentTests { [Test] diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs index bcce1b592..d67f4d558 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs @@ -14,6 +14,7 @@ using static LanguageExt.Prelude; namespace ErsatzTV.Core.Tests.Scheduling { + [TestFixture] public class PlayoutBuilderTests { private readonly ILogger _logger; diff --git a/ErsatzTV.Core.Tests/Scheduling/RandomizedContentTests.cs b/ErsatzTV.Core.Tests/Scheduling/RandomizedContentTests.cs index d02509176..8992c6ede 100644 --- a/ErsatzTV.Core.Tests/Scheduling/RandomizedContentTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/RandomizedContentTests.cs @@ -9,6 +9,7 @@ using static LanguageExt.Prelude; namespace ErsatzTV.Core.Tests.Scheduling { + [TestFixture] public class RandomizedContentTests { private const int KnownSeed = 22295; diff --git a/ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs b/ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs index 65c28120d..f767f3f12 100644 --- a/ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs @@ -9,6 +9,7 @@ using static LanguageExt.Prelude; namespace ErsatzTV.Core.Tests.Scheduling { + [TestFixture] public class ShuffledContentTests { // this seed will produce (shuffle) 1-10 in order diff --git a/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs b/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs new file mode 100644 index 000000000..a0c10c659 --- /dev/null +++ b/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs @@ -0,0 +1,30 @@ +using System.IO; +using System.Text.RegularExpressions; +using ErsatzTV.Core.Domain; + +namespace ErsatzTV.Core.Metadata +{ + public static class FallbackMetadataProvider + { + public static MediaMetadata GetFallbackMetadata(string path) + { + string fileName = Path.GetFileName(path); + var metadata = new MediaMetadata { Title = fileName ?? path }; + + if (fileName != null) + { + const string PATTERN = @"^(.*?)[.\s-]+[sS](\d+)[eE](\d+).*\.\w+$"; + Match match = Regex.Match(fileName, PATTERN); + if (match.Success) + { + metadata.MediaType = MediaType.TvShow; + metadata.Title = match.Groups[1].Value; + metadata.SeasonNumber = int.Parse(match.Groups[2].Value); + metadata.EpisodeNumber = int.Parse(match.Groups[3].Value); + } + } + + return metadata; + } + } +} diff --git a/ErsatzTV.Core/Metadata/LocalMediaScanner.cs b/ErsatzTV.Core/Metadata/LocalMediaScanner.cs index 315648760..df3603b8b 100644 --- a/ErsatzTV.Core/Metadata/LocalMediaScanner.cs +++ b/ErsatzTV.Core/Metadata/LocalMediaScanner.cs @@ -82,12 +82,13 @@ namespace ErsatzTV.Core.Metadata } // if exists, check if the file was modified + // also, try to re-categorize "other" by refreshing metadata Seq modifiedMediaItems = existingMediaItems.Filter( mediaItem => { DateTime lastWrite = File.GetLastWriteTimeUtc(mediaItem.Path); bool modified = lastWrite > mediaItem.LastWriteTime.IfNone(DateTime.MinValue); - return modified || mediaItem.Metadata == null; + return modified || mediaItem.Metadata == null || mediaItem.Metadata.MediaType == MediaType.Other; }); modifiedPlayoutIds.AddRange(await _playoutRepository.GetPlayoutIdsForMediaItems(modifiedMediaItems)); foreach (MediaItem mediaItem in modifiedMediaItems) diff --git a/ErsatzTV.Core/Metadata/LocalMetadataProvider.cs b/ErsatzTV.Core/Metadata/LocalMetadataProvider.cs index 334a6bfeb..62fca8884 100644 --- a/ErsatzTV.Core/Metadata/LocalMetadataProvider.cs +++ b/ErsatzTV.Core/Metadata/LocalMetadataProvider.cs @@ -21,7 +21,8 @@ namespace ErsatzTV.Core.Metadata public async Task RefreshMetadata(MediaItem mediaItem) { Option maybeMetadata = await LoadMetadata(mediaItem); - MediaMetadata metadata = maybeMetadata.IfNone(() => GetFallbackMetadata(mediaItem)); + MediaMetadata metadata = + maybeMetadata.IfNone(() => FallbackMetadataProvider.GetFallbackMetadata(mediaItem.Path)); await ApplyMetadataUpdate(mediaItem, metadata); } @@ -99,27 +100,6 @@ namespace ErsatzTV.Core.Metadata None); } - private MediaMetadata GetFallbackMetadata(MediaItem mediaItem) - { - string fileName = Path.GetFileName(mediaItem.Path); - var metadata = new MediaMetadata { Title = fileName ?? mediaItem.Path }; - - if (fileName != null) - { - const string PATTERN = @"^(.*?)[\s-]+[sS](\d+)[eE](\d+)\.\w+$"; - Match match = Regex.Match(fileName, PATTERN); - if (match.Success) - { - metadata.MediaType = MediaType.TvShow; - metadata.Title = match.Groups[1].Value; - metadata.SeasonNumber = int.Parse(match.Groups[2].Value); - metadata.EpisodeNumber = int.Parse(match.Groups[3].Value); - } - } - - return metadata; - } - private static DateTime? GetAired(string aired) { if (string.IsNullOrWhiteSpace(aired)) diff --git a/ErsatzTV/Pages/MediaCollections.razor b/ErsatzTV/Pages/MediaCollections.razor index 52dd0fa2d..d34c2523a 100644 --- a/ErsatzTV/Pages/MediaCollections.razor +++ b/ErsatzTV/Pages/MediaCollections.razor @@ -91,7 +91,11 @@ _totalItems = aggregateData.Count; - _pagedData = aggregateData.Skip(state.Page * state.PageSize).Take(state.PageSize); + _pagedData = aggregateData + .Skip(_totalItems <= state.PageSize ? 0 : state.Page * state.PageSize) + .Take(state.PageSize) + .OrderBy(c => c.Name); + return new TableData { TotalItems = _totalItems, Items = _pagedData }; } diff --git a/ErsatzTV/Pages/PlayoutEditor.razor b/ErsatzTV/Pages/PlayoutEditor.razor index 9e3c66b48..edb74cb55 100644 --- a/ErsatzTV/Pages/PlayoutEditor.razor +++ b/ErsatzTV/Pages/PlayoutEditor.razor @@ -49,10 +49,10 @@ } private Task> SearchChannels(string value) => - _channels.Filter(c => $"{c.Number} - {c.Name}".ToLowerInvariant().Contains(value ?? string.Empty)).AsTask(); + _channels.Filter(c => $"{c.Number} - {c.Name}".Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask(); private Task> SearchProgramSchedules(string value) => - _programSchedules.Filter(c => c.Name.ToLowerInvariant().StartsWith(value ?? string.Empty)).AsTask(); + _programSchedules.Filter(c => c.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask(); private async Task HandleSubmitAsync() diff --git a/ErsatzTV/Pages/ScheduleItemsEditor.razor b/ErsatzTV/Pages/ScheduleItemsEditor.razor index eac6db5c3..a73ea18ce 100644 --- a/ErsatzTV/Pages/ScheduleItemsEditor.razor +++ b/ErsatzTV/Pages/ScheduleItemsEditor.razor @@ -170,7 +170,7 @@ } private Task> SearchMediaCollections(string value) => - _mediaCollections.Filter(c => c.Name.ToLowerInvariant().Contains((value ?? string.Empty).ToLowerInvariant())).AsTask(); + _mediaCollections.Filter(c => c.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask(); private async Task SaveChanges() {