mirror of https://github.com/ErsatzTV/ErsatzTV.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
188 lines
6.6 KiB
188 lines
6.6 KiB
using System; |
|
using System.IO; |
|
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, 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)); |
|
await ApplyMetadataUpdate(mediaItem, metadata); |
|
} |
|
|
|
private async Task ApplyMetadataUpdate(MediaItem mediaItem, MediaMetadata metadata) |
|
{ |
|
if (mediaItem.Metadata == null) |
|
{ |
|
mediaItem.Metadata = new MediaMetadata(); |
|
} |
|
|
|
mediaItem.Metadata.MediaType = metadata.MediaType; |
|
mediaItem.Metadata.Title = metadata.Title; |
|
mediaItem.Metadata.Subtitle = metadata.Subtitle; |
|
mediaItem.Metadata.SortTitle = |
|
(metadata.Title ?? string.Empty).ToLowerInvariant().StartsWith("the ") |
|
? metadata.Title?.Substring(4) |
|
: metadata.Title; |
|
mediaItem.Metadata.Description = metadata.Description; |
|
mediaItem.Metadata.EpisodeNumber = metadata.EpisodeNumber; |
|
mediaItem.Metadata.SeasonNumber = metadata.SeasonNumber; |
|
mediaItem.Metadata.Aired = metadata.Aired; |
|
mediaItem.Metadata.ContentRating = metadata.ContentRating; |
|
|
|
await _mediaItemRepository.Update(mediaItem); |
|
} |
|
|
|
private async Task<Option<MediaMetadata>> LoadMetadata(MediaItem mediaItem) |
|
{ |
|
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; |
|
} |
|
|
|
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 |
|
{ |
|
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) |
|
{ |
|
if (string.IsNullOrWhiteSpace(aired)) |
|
{ |
|
return null; |
|
} |
|
|
|
if (DateTime.TryParse(aired, out DateTime parsed)) |
|
{ |
|
return parsed; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
[XmlRoot("movie")] |
|
public class MovieNfo |
|
{ |
|
[XmlElement("title")] |
|
public string Title { get; set; } |
|
|
|
[XmlElement("outline")] |
|
public string Outline { get; set; } |
|
|
|
[XmlElement("mpaa")] |
|
public string ContentRating { get; set; } |
|
|
|
[XmlElement("premiered")] |
|
public string Premiered { get; set; } |
|
} |
|
|
|
[XmlRoot("episodedetails")] |
|
public class TvShowEpisodeNfo |
|
{ |
|
[XmlElement("showtitle")] |
|
public string ShowTitle { get; set; } |
|
|
|
[XmlElement("title")] |
|
public string Title { get; set; } |
|
|
|
[XmlElement("outline")] |
|
public string Outline { get; set; } |
|
|
|
[XmlElement("episode")] |
|
public int Episode { get; set; } |
|
|
|
[XmlElement("season")] |
|
public int Season { get; set; } |
|
|
|
[XmlElement("mpaa")] |
|
public string ContentRating { get; set; } |
|
|
|
[XmlElement("aired")] |
|
public string Aired { get; set; } |
|
} |
|
} |
|
}
|
|
|