Browse Source

Movie metadata fixes (#9)

* reorganize metadata parsing; only attempt to parse appropriate media type based on media source configuration

* add fallback metadata for movie sources

* only request read access for nfo metadata

* fix tests
pull/10/head
Jason Dove 5 years ago committed by GitHub
parent
commit
a3e20826a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs
  2. 54
      ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs
  3. 119
      ErsatzTV.Core/Metadata/LocalMetadataProvider.cs

15
ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs

@ -25,11 +25,20 @@ namespace ErsatzTV.Core.Tests.Metadata @@ -25,11 +25,20 @@ 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, Source = new LocalMediaSource { MediaType = MediaType.TvShow } });
metadata.MediaType.Should().Be(MediaType.TvShow);
metadata.Title.Should().Be(title);

54
ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs

@ -1,4 +1,5 @@ @@ -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 @@ -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 @@ -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;
}

119
ErsatzTV.Core/Metadata/LocalMetadataProvider.cs

@ -1,28 +1,35 @@ @@ -1,28 +1,35 @@
using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml.Serialization;
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
{
public class LocalMetadataProvider : ILocalMetadataProvider
{
private static readonly XmlSerializer MovieSerializer = new(typeof(MovieNfo));
private static readonly XmlSerializer TvShowSerializer = new(typeof(TvShowEpisodeNfo));
private readonly ILogger<LocalMetadataProvider> _logger;
private readonly IMediaItemRepository _mediaItemRepository;
public LocalMetadataProvider(IMediaItemRepository mediaItemRepository) =>
public LocalMetadataProvider(IMediaItemRepository mediaItemRepository, ILogger<LocalMetadataProvider> logger)
{
_mediaItemRepository = mediaItemRepository;
_logger = logger;
}
public async Task RefreshMetadata(MediaItem mediaItem)
{
Option<MediaMetadata> maybeMetadata = await LoadMetadata(mediaItem);
MediaMetadata metadata =
maybeMetadata.IfNone(() => FallbackMetadataProvider.GetFallbackMetadata(mediaItem.Path));
maybeMetadata.IfNone(() => FallbackMetadataProvider.GetFallbackMetadata(mediaItem));
await ApplyMetadataUpdate(mediaItem, metadata);
}
@ -50,54 +57,72 @@ namespace ErsatzTV.Core.Metadata @@ -50,54 +57,72 @@ 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;
}
var tvShowSerializer = new XmlSerializer(typeof(TvShowEpisodeNfo));
var movieSerializer = new XmlSerializer(typeof(MovieNfo));
TryAsync<object> tvShowAttempt = TryAsync(
async () =>
{
await using FileStream fileStream = File.Open(nfoFileName, FileMode.Open);
return tvShowSerializer.Deserialize(fileStream);
});
TryAsync<object> movieAttempt = TryAsync(
async () =>
{
await using FileStream fileStream = File.Open(nfoFileName, FileMode.Open);
return movieSerializer.Deserialize(fileStream);
});
return await choice(tvShowAttempt, movieAttempt).Match<object, Option<MediaMetadata>>(
result =>
{
switch (result)
return localMediaSource.MediaType switch
{
MediaType.Movie => await LoadMovieMetadata(nfoFileName),
MediaType.TvShow => await LoadTvShowMetadata(nfoFileName),
_ => None
};
}
private async Task<Option<MediaMetadata>> LoadTvShowMetadata(string nfoFileName)
{
try
{
await using FileStream fileStream = File.Open(nfoFileName, FileMode.Open, FileAccess.Read);
Option<TvShowEpisodeNfo> maybeNfo = TvShowSerializer.Deserialize(fileStream) as TvShowEpisodeNfo;
return maybeNfo.Match<Option<MediaMetadata>>(
nfo => new MediaMetadata
{
case TvShowEpisodeNfo nfo:
return new MediaMetadata
{
MediaType = MediaType.TvShow,
Title = nfo.ShowTitle,
Subtitle = nfo.Title,
Description = nfo.Outline,
EpisodeNumber = nfo.Episode,
SeasonNumber = nfo.Season,
Aired = GetAired(nfo.Aired)
};
case MovieNfo nfo:
return new MediaMetadata
{
MediaType = MediaType.Movie,
Title = nfo.Title,
Description = nfo.Outline,
ContentRating = nfo.ContentRating,
Aired = GetAired(nfo.Premiered)
};
default:
return None;
}
},
None);
MediaType = MediaType.TvShow,
Title = nfo.ShowTitle,
Subtitle = nfo.Title,
Description = nfo.Outline,
EpisodeNumber = nfo.Episode,
SeasonNumber = nfo.Season,
Aired = GetAired(nfo.Aired)
},
None);
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Failed to read TV nfo metadata from {Path}", nfoFileName);
return None;
}
}
private async Task<Option<MediaMetadata>> LoadMovieMetadata(string nfoFileName)
{
try
{
await using FileStream fileStream = File.Open(nfoFileName, FileMode.Open, FileAccess.Read);
Option<MovieNfo> maybeNfo = MovieSerializer.Deserialize(fileStream) as MovieNfo;
return maybeNfo.Match<Option<MediaMetadata>>(
nfo => new MediaMetadata
{
MediaType = MediaType.Movie,
Title = nfo.Title,
Description = nfo.Outline,
ContentRating = nfo.ContentRating,
Aired = GetAired(nfo.Premiered)
},
None);
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Failed to read Movie nfo metadata from {Path}", nfoFileName);
return None;
}
}
private static DateTime? GetAired(string aired)

Loading…
Cancel
Save