diff --git a/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs b/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs index 2e2500abc..6453a9dac 100644 --- a/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs +++ b/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs @@ -25,11 +25,19 @@ namespace ErsatzTV.Core.Tests.Metadata [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)] + [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); + MediaMetadata metadata = FallbackMetadataProvider.GetFallbackMetadata(new MediaItem { Path = path }); metadata.MediaType.Should().Be(MediaType.TvShow); metadata.Title.Should().Be(title); diff --git a/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs b/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs index a0c10c659..0723ec7fd 100644 --- a/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs +++ b/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Text.RegularExpressions; using ErsatzTV.Core.Domain; @@ -6,12 +7,32 @@ namespace ErsatzTV.Core.Metadata { public static class FallbackMetadataProvider { - public static MediaMetadata GetFallbackMetadata(string path) + public static MediaMetadata GetFallbackMetadata(MediaItem mediaItem) { - string fileName = Path.GetFileName(path); - var metadata = new MediaMetadata { Title = fileName ?? path }; + string fileName = Path.GetFileName(mediaItem.Path); + var metadata = new MediaMetadata { Title = fileName ?? mediaItem.Path }; if (fileName != null) + { + if (!(mediaItem.Source is LocalMediaSource localMediaSource)) + { + return metadata; + } + + return localMediaSource.MediaType switch + { + MediaType.TvShow => GetTvShowMetadata(fileName, metadata), + MediaType.Movie => GetMovieMetadata(fileName, metadata), + _ => metadata + }; + } + + return metadata; + } + + private static MediaMetadata GetTvShowMetadata(string fileName, MediaMetadata metadata) + { + try { const string PATTERN = @"^(.*?)[.\s-]+[sS](\d+)[eE](\d+).*\.\w+$"; Match match = Regex.Match(fileName, PATTERN); @@ -23,6 +44,31 @@ namespace ErsatzTV.Core.Metadata metadata.EpisodeNumber = int.Parse(match.Groups[3].Value); } } + catch (Exception) + { + // ignored + } + + return metadata; + } + + private static MediaMetadata GetMovieMetadata(string fileName, MediaMetadata metadata) + { + try + { + const string PATTERN = @"^(.*?)[.\(](\d{4})[.\)].*\.\w+$"; + Match match = Regex.Match(fileName, PATTERN); + if (match.Success) + { + metadata.MediaType = MediaType.Movie; + metadata.Title = match.Groups[1].Value; + metadata.Aired = new DateTime(int.Parse(match.Groups[2].Value), 1, 1); + } + } + catch (Exception) + { + // ignored + } return metadata; } diff --git a/ErsatzTV.Core/Metadata/LocalMetadataProvider.cs b/ErsatzTV.Core/Metadata/LocalMetadataProvider.cs index c7cfa4861..1eda0e3c6 100644 --- a/ErsatzTV.Core/Metadata/LocalMetadataProvider.cs +++ b/ErsatzTV.Core/Metadata/LocalMetadataProvider.cs @@ -6,6 +6,7 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using LanguageExt; +using Microsoft.Extensions.Logging; using static LanguageExt.Prelude; namespace ErsatzTV.Core.Metadata @@ -14,17 +15,21 @@ namespace ErsatzTV.Core.Metadata { private static readonly XmlSerializer MovieSerializer = new(typeof(MovieNfo)); private static readonly XmlSerializer TvShowSerializer = new(typeof(TvShowEpisodeNfo)); + private readonly ILogger _logger; private readonly IMediaItemRepository _mediaItemRepository; - public LocalMetadataProvider(IMediaItemRepository mediaItemRepository) => + public LocalMetadataProvider(IMediaItemRepository mediaItemRepository, ILogger logger) + { _mediaItemRepository = mediaItemRepository; + _logger = logger; + } public async Task RefreshMetadata(MediaItem mediaItem) { Option maybeMetadata = await LoadMetadata(mediaItem); MediaMetadata metadata = - maybeMetadata.IfNone(() => FallbackMetadataProvider.GetFallbackMetadata(mediaItem.Path)); + maybeMetadata.IfNone(() => FallbackMetadataProvider.GetFallbackMetadata(mediaItem)); await ApplyMetadataUpdate(mediaItem, metadata); } @@ -52,11 +57,13 @@ namespace ErsatzTV.Core.Metadata string nfoFileName = Path.ChangeExtension(mediaItem.Path, "nfo"); if (nfoFileName == null || !File.Exists(nfoFileName)) { + _logger.LogDebug("NFO file does not exist at {Path}", nfoFileName); return None; } if (!(mediaItem.Source is LocalMediaSource localMediaSource)) { + _logger.LogDebug("Media source {Name} is not a local media source", mediaItem.Source.Name); return None; } @@ -68,7 +75,7 @@ namespace ErsatzTV.Core.Metadata }; } - private static async Task> LoadTvShowMetadata(string nfoFileName) + private async Task> LoadTvShowMetadata(string nfoFileName) { try { @@ -87,13 +94,14 @@ namespace ErsatzTV.Core.Metadata }, None); } - catch (Exception) + catch (Exception ex) { + _logger.LogDebug(ex, "Failed to read TV nfo metadata from {Path}", nfoFileName); return None; } } - private static async Task> LoadMovieMetadata(string nfoFileName) + private async Task> LoadMovieMetadata(string nfoFileName) { try { @@ -110,8 +118,9 @@ namespace ErsatzTV.Core.Metadata }, None); } - catch (Exception) + catch (Exception ex) { + _logger.LogDebug(ex, "Failed to read Movie nfo metadata from {Path}", nfoFileName); return None; } }