Fix fallback metadata parsing for TV (#3)
* fix yml formatting * test build action * move ci workflow * fix unit tests from dotnet test command * add release workflow * try another matrix syntax * fix path to csproj * fix target framework * more release fixes * port fixes for running outside of docker * more search fixes * improve fallback metadata parsing, add tests * fix odd behavior when searching collections * more fallback metadata fixes * refresh "other" metadata on startup
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -25,7 +26,7 @@ namespace ErsatzTV.Application.MediaItems.Queries
|
|||||||
if (!string.IsNullOrEmpty(request.SearchString))
|
if (!string.IsNullOrEmpty(request.SearchString))
|
||||||
{
|
{
|
||||||
allItems = allItems.Filter(
|
allItems = allItems.Filter(
|
||||||
i => i.Metadata?.Title.ToLowerInvariant().Contains(request.SearchString.ToLowerInvariant()) ==
|
i => i.Metadata?.Title.Contains(request.SearchString, StringComparison.OrdinalIgnoreCase) ==
|
||||||
true);
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using NUnit.Framework;
|
|||||||
|
|
||||||
namespace ErsatzTV.Core.Tests.FFmpeg
|
namespace ErsatzTV.Core.Tests.FFmpeg
|
||||||
{
|
{
|
||||||
|
[TestFixture]
|
||||||
public class FFmpegPlaybackSettingsCalculatorTests
|
public class FFmpegPlaybackSettingsCalculatorTests
|
||||||
{
|
{
|
||||||
public class CalculateSettings
|
public class CalculateSettings
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ using static LanguageExt.Prelude;
|
|||||||
|
|
||||||
namespace ErsatzTV.Core.Tests.Scheduling
|
namespace ErsatzTV.Core.Tests.Scheduling
|
||||||
{
|
{
|
||||||
|
[TestFixture]
|
||||||
public class ChronologicalContentTests
|
public class ChronologicalContentTests
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using static LanguageExt.Prelude;
|
|||||||
|
|
||||||
namespace ErsatzTV.Core.Tests.Scheduling
|
namespace ErsatzTV.Core.Tests.Scheduling
|
||||||
{
|
{
|
||||||
|
[TestFixture]
|
||||||
public class PlayoutBuilderTests
|
public class PlayoutBuilderTests
|
||||||
{
|
{
|
||||||
private readonly ILogger<PlayoutBuilder> _logger;
|
private readonly ILogger<PlayoutBuilder> _logger;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using static LanguageExt.Prelude;
|
|||||||
|
|
||||||
namespace ErsatzTV.Core.Tests.Scheduling
|
namespace ErsatzTV.Core.Tests.Scheduling
|
||||||
{
|
{
|
||||||
|
[TestFixture]
|
||||||
public class RandomizedContentTests
|
public class RandomizedContentTests
|
||||||
{
|
{
|
||||||
private const int KnownSeed = 22295;
|
private const int KnownSeed = 22295;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using static LanguageExt.Prelude;
|
|||||||
|
|
||||||
namespace ErsatzTV.Core.Tests.Scheduling
|
namespace ErsatzTV.Core.Tests.Scheduling
|
||||||
{
|
{
|
||||||
|
[TestFixture]
|
||||||
public class ShuffledContentTests
|
public class ShuffledContentTests
|
||||||
{
|
{
|
||||||
// this seed will produce (shuffle) 1-10 in order
|
// this seed will produce (shuffle) 1-10 in order
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -82,12 +82,13 @@ namespace ErsatzTV.Core.Metadata
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if exists, check if the file was modified
|
// if exists, check if the file was modified
|
||||||
|
// also, try to re-categorize "other" by refreshing metadata
|
||||||
Seq<MediaItem> modifiedMediaItems = existingMediaItems.Filter(
|
Seq<MediaItem> modifiedMediaItems = existingMediaItems.Filter(
|
||||||
mediaItem =>
|
mediaItem =>
|
||||||
{
|
{
|
||||||
DateTime lastWrite = File.GetLastWriteTimeUtc(mediaItem.Path);
|
DateTime lastWrite = File.GetLastWriteTimeUtc(mediaItem.Path);
|
||||||
bool modified = lastWrite > mediaItem.LastWriteTime.IfNone(DateTime.MinValue);
|
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));
|
modifiedPlayoutIds.AddRange(await _playoutRepository.GetPlayoutIdsForMediaItems(modifiedMediaItems));
|
||||||
foreach (MediaItem mediaItem in modifiedMediaItems)
|
foreach (MediaItem mediaItem in modifiedMediaItems)
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ namespace ErsatzTV.Core.Metadata
|
|||||||
public async Task RefreshMetadata(MediaItem mediaItem)
|
public async Task RefreshMetadata(MediaItem mediaItem)
|
||||||
{
|
{
|
||||||
Option<MediaMetadata> maybeMetadata = await LoadMetadata(mediaItem);
|
Option<MediaMetadata> maybeMetadata = await LoadMetadata(mediaItem);
|
||||||
MediaMetadata metadata = maybeMetadata.IfNone(() => GetFallbackMetadata(mediaItem));
|
MediaMetadata metadata =
|
||||||
|
maybeMetadata.IfNone(() => FallbackMetadataProvider.GetFallbackMetadata(mediaItem.Path));
|
||||||
await ApplyMetadataUpdate(mediaItem, metadata);
|
await ApplyMetadataUpdate(mediaItem, metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,27 +100,6 @@ namespace ErsatzTV.Core.Metadata
|
|||||||
None);
|
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)
|
private static DateTime? GetAired(string aired)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(aired))
|
if (string.IsNullOrWhiteSpace(aired))
|
||||||
|
|||||||
@@ -91,7 +91,11 @@
|
|||||||
|
|
||||||
_totalItems = aggregateData.Count;
|
_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<MediaCollectionSummaryViewModel> { TotalItems = _totalItems, Items = _pagedData };
|
return new TableData<MediaCollectionSummaryViewModel> { TotalItems = _totalItems, Items = _pagedData };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,10 +49,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Task<IEnumerable<ChannelViewModel>> SearchChannels(string value) =>
|
private Task<IEnumerable<ChannelViewModel>> 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<IEnumerable<ProgramScheduleViewModel>> SearchProgramSchedules(string value) =>
|
private Task<IEnumerable<ProgramScheduleViewModel>> 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()
|
private async Task HandleSubmitAsync()
|
||||||
|
|||||||
@@ -170,7 +170,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Task<IEnumerable<MediaCollectionViewModel>> SearchMediaCollections(string value) =>
|
private Task<IEnumerable<MediaCollectionViewModel>> 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()
|
private async Task SaveChanges()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user