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.
1443 lines
54 KiB
1443 lines
54 KiB
using Bugsnag; |
|
using ErsatzTV.Core.Domain; |
|
using ErsatzTV.Core.Extensions; |
|
using ErsatzTV.Core.Interfaces.Metadata; |
|
using ErsatzTV.Core.Interfaces.Metadata.Nfo; |
|
using ErsatzTV.Core.Interfaces.Repositories; |
|
using ErsatzTV.Core.Metadata.Nfo; |
|
using Microsoft.Extensions.Logging; |
|
|
|
namespace ErsatzTV.Core.Metadata; |
|
|
|
public class LocalMetadataProvider : ILocalMetadataProvider |
|
{ |
|
private readonly IArtistNfoReader _artistNfoReader; |
|
private readonly IArtistRepository _artistRepository; |
|
private readonly IClient _client; |
|
private readonly IEpisodeNfoReader _episodeNfoReader; |
|
private readonly IFallbackMetadataProvider _fallbackMetadataProvider; |
|
private readonly ILocalFileSystem _localFileSystem; |
|
private readonly ILocalStatisticsProvider _localStatisticsProvider; |
|
private readonly ILogger<LocalMetadataProvider> _logger; |
|
private readonly IMetadataRepository _metadataRepository; |
|
private readonly IMovieNfoReader _movieNfoReader; |
|
private readonly IMovieRepository _movieRepository; |
|
private readonly IMusicVideoNfoReader _musicVideoNfoReader; |
|
private readonly IMusicVideoRepository _musicVideoRepository; |
|
private readonly IOtherVideoNfoReader _otherVideoNfoReader; |
|
private readonly IOtherVideoRepository _otherVideoRepository; |
|
private readonly ISongRepository _songRepository; |
|
private readonly ITelevisionRepository _televisionRepository; |
|
private readonly ITvShowNfoReader _tvShowNfoReader; |
|
|
|
public LocalMetadataProvider( |
|
IMetadataRepository metadataRepository, |
|
IMovieRepository movieRepository, |
|
ITelevisionRepository televisionRepository, |
|
IArtistRepository artistRepository, |
|
IMusicVideoRepository musicVideoRepository, |
|
IOtherVideoRepository otherVideoRepository, |
|
ISongRepository songRepository, |
|
IFallbackMetadataProvider fallbackMetadataProvider, |
|
ILocalFileSystem localFileSystem, |
|
IMovieNfoReader movieNfoReader, |
|
IEpisodeNfoReader episodeNfoReader, |
|
IArtistNfoReader artistNfoReader, |
|
IMusicVideoNfoReader musicVideoNfoReader, |
|
ITvShowNfoReader tvShowNfoReader, |
|
IOtherVideoNfoReader otherVideoNfoReader, |
|
ILocalStatisticsProvider localStatisticsProvider, |
|
IClient client, |
|
ILogger<LocalMetadataProvider> logger) |
|
{ |
|
_metadataRepository = metadataRepository; |
|
_movieRepository = movieRepository; |
|
_televisionRepository = televisionRepository; |
|
_artistRepository = artistRepository; |
|
_musicVideoRepository = musicVideoRepository; |
|
_otherVideoRepository = otherVideoRepository; |
|
_songRepository = songRepository; |
|
_fallbackMetadataProvider = fallbackMetadataProvider; |
|
_localFileSystem = localFileSystem; |
|
_movieNfoReader = movieNfoReader; |
|
_episodeNfoReader = episodeNfoReader; |
|
_artistNfoReader = artistNfoReader; |
|
_musicVideoNfoReader = musicVideoNfoReader; |
|
_tvShowNfoReader = tvShowNfoReader; |
|
_otherVideoNfoReader = otherVideoNfoReader; |
|
_localStatisticsProvider = localStatisticsProvider; |
|
_client = client; |
|
_logger = logger; |
|
} |
|
|
|
public async Task<ShowMetadata> GetMetadataForShow(string showFolder) |
|
{ |
|
string nfoFileName = Path.Combine(showFolder, "tvshow.nfo"); |
|
Option<ShowMetadata> maybeMetadata = None; |
|
if (_localFileSystem.FileExists(nfoFileName)) |
|
{ |
|
maybeMetadata = await LoadTelevisionShowMetadata(nfoFileName); |
|
} |
|
|
|
foreach (ShowMetadata metadata in maybeMetadata) |
|
{ |
|
metadata.SortTitle = _fallbackMetadataProvider.GetSortTitle(metadata.Title); |
|
return metadata; |
|
} |
|
|
|
ShowMetadata fallbackMetadata = _fallbackMetadataProvider.GetFallbackMetadataForShow(showFolder); |
|
fallbackMetadata.SortTitle = _fallbackMetadataProvider.GetSortTitle(fallbackMetadata.Title); |
|
return fallbackMetadata; |
|
} |
|
|
|
public async Task<ArtistMetadata> GetMetadataForArtist(string artistFolder) |
|
{ |
|
string nfoFileName = Path.Combine(artistFolder, "artist.nfo"); |
|
Option<ArtistMetadata> maybeMetadata = None; |
|
if (_localFileSystem.FileExists(nfoFileName)) |
|
{ |
|
maybeMetadata = await LoadArtistMetadata(nfoFileName); |
|
} |
|
|
|
foreach (ArtistMetadata metadata in maybeMetadata) |
|
{ |
|
metadata.SortTitle = _fallbackMetadataProvider.GetSortTitle(metadata.Title); |
|
return metadata; |
|
} |
|
|
|
ArtistMetadata fallbackMetadata = _fallbackMetadataProvider.GetFallbackMetadataForArtist(artistFolder); |
|
fallbackMetadata.SortTitle = _fallbackMetadataProvider.GetSortTitle(fallbackMetadata.Title); |
|
return fallbackMetadata; |
|
} |
|
|
|
public async Task<bool> RefreshSidecarMetadata(Movie movie, string nfoFileName) |
|
{ |
|
Option<MovieMetadata> maybeMetadata = await LoadMovieMetadata(movie, nfoFileName); |
|
foreach (MovieMetadata metadata in maybeMetadata) |
|
{ |
|
return await ApplyMetadataUpdate(movie, metadata); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
public async Task<bool> RefreshSidecarMetadata(Show televisionShow, string nfoFileName) |
|
{ |
|
Option<ShowMetadata> maybeMetadata = await LoadTelevisionShowMetadata(nfoFileName); |
|
foreach (ShowMetadata metadata in maybeMetadata) |
|
{ |
|
return await ApplyMetadataUpdate(televisionShow, metadata); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
public async Task<bool> RefreshSidecarMetadata(Episode episode, string nfoFileName) |
|
{ |
|
List<EpisodeMetadata> metadata = await LoadEpisodeMetadata(episode, nfoFileName); |
|
return await ApplyMetadataUpdate(episode, metadata); |
|
} |
|
|
|
public async Task<bool> RefreshSidecarMetadata(Artist artist, string nfoFileName) |
|
{ |
|
Option<ArtistMetadata> maybeMetadata = await LoadArtistMetadata(nfoFileName); |
|
foreach (ArtistMetadata metadata in maybeMetadata) |
|
{ |
|
return await ApplyMetadataUpdate(artist, metadata); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
public async Task<bool> RefreshSidecarMetadata(MusicVideo musicVideo, string nfoFileName) |
|
{ |
|
Option<MusicVideoMetadata> maybeMetadata = await LoadMusicVideoMetadata(nfoFileName); |
|
foreach (MusicVideoMetadata metadata in maybeMetadata) |
|
{ |
|
return await ApplyMetadataUpdate(musicVideo, metadata); |
|
} |
|
|
|
return await RefreshFallbackMetadata(musicVideo); |
|
} |
|
|
|
public async Task<bool> RefreshSidecarMetadata(OtherVideo otherVideo, string nfoFileName) |
|
{ |
|
Option<OtherVideoMetadata> maybeMetadata = await LoadOtherVideoMetadata(nfoFileName); |
|
foreach (OtherVideoMetadata metadata in maybeMetadata) |
|
{ |
|
return await ApplyMetadataUpdate(otherVideo, metadata); |
|
} |
|
|
|
return await RefreshFallbackMetadata(otherVideo); |
|
} |
|
|
|
public async Task<bool> RefreshTagMetadata(Song song, string ffprobePath) |
|
{ |
|
Option<SongMetadata> maybeMetadata = await LoadSongMetadata(song, ffprobePath); |
|
foreach (SongMetadata metadata in maybeMetadata) |
|
{ |
|
return await ApplyMetadataUpdate(song, metadata); |
|
} |
|
|
|
return await RefreshFallbackMetadata(song); |
|
} |
|
|
|
public Task<bool> RefreshFallbackMetadata(Movie movie) => |
|
ApplyMetadataUpdate(movie, _fallbackMetadataProvider.GetFallbackMetadata(movie)); |
|
|
|
public Task<bool> RefreshFallbackMetadata(Episode episode) => |
|
ApplyMetadataUpdate(episode, _fallbackMetadataProvider.GetFallbackMetadata(episode)); |
|
|
|
public Task<bool> RefreshFallbackMetadata(Artist artist, string artistFolder) => |
|
ApplyMetadataUpdate(artist, _fallbackMetadataProvider.GetFallbackMetadataForArtist(artistFolder)); |
|
|
|
public async Task<bool> RefreshFallbackMetadata(OtherVideo otherVideo) |
|
{ |
|
Option<OtherVideoMetadata> maybeMetadata = _fallbackMetadataProvider.GetFallbackMetadata(otherVideo); |
|
foreach (OtherVideoMetadata metadata in maybeMetadata) |
|
{ |
|
return await ApplyMetadataUpdate(otherVideo, metadata); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
public async Task<bool> RefreshFallbackMetadata(Song song) |
|
{ |
|
Option<SongMetadata> maybeMetadata = _fallbackMetadataProvider.GetFallbackMetadata(song); |
|
foreach (SongMetadata metadata in maybeMetadata) |
|
{ |
|
return await ApplyMetadataUpdate(song, metadata); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
public async Task<bool> RefreshFallbackMetadata(MusicVideo musicVideo) |
|
{ |
|
Option<MusicVideoMetadata> maybeMetadata = _fallbackMetadataProvider.GetFallbackMetadata(musicVideo); |
|
foreach (MusicVideoMetadata metadata in maybeMetadata) |
|
{ |
|
return await ApplyMetadataUpdate(musicVideo, metadata); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
public Task<bool> RefreshFallbackMetadata(Show televisionShow, string showFolder) => |
|
ApplyMetadataUpdate(televisionShow, _fallbackMetadataProvider.GetFallbackMetadataForShow(showFolder)); |
|
|
|
private async Task<Option<MusicVideoMetadata>> LoadMusicVideoMetadata(string nfoFileName) |
|
{ |
|
try |
|
{ |
|
Either<BaseError, MusicVideoNfo> maybeNfo = await _musicVideoNfoReader.ReadFromFile(nfoFileName); |
|
foreach (BaseError error in maybeNfo.LeftToSeq()) |
|
{ |
|
_logger.LogInformation( |
|
"Failed to read MusicVideo nfo metadata from {Path}: {Error}", |
|
nfoFileName, |
|
error.ToString()); |
|
} |
|
|
|
foreach (MusicVideoNfo nfo in maybeNfo.RightToSeq()) |
|
{ |
|
return new MusicVideoMetadata |
|
{ |
|
MetadataKind = MetadataKind.Sidecar, |
|
DateAdded = DateTime.UtcNow, |
|
DateUpdated = File.GetLastWriteTimeUtc(nfoFileName), |
|
Album = nfo.Album, |
|
Title = nfo.Title, |
|
Plot = nfo.Plot, |
|
Track = nfo.Track, |
|
Year = GetYear(nfo.Year, nfo.Aired), |
|
ReleaseDate = GetAired(nfo.Year, nfo.Aired), |
|
Artists = nfo.Artists.Map(a => new MusicVideoArtist { Name = a }).ToList(), |
|
Genres = nfo.Genres.Map(g => new Genre { Name = g }).ToList(), |
|
Tags = nfo.Tags.Map(t => new Tag { Name = t }).ToList(), |
|
Studios = nfo.Studios.Map(s => new Studio { Name = s }).ToList() |
|
}; |
|
} |
|
|
|
return None; |
|
} |
|
catch (Exception ex) |
|
{ |
|
_logger.LogInformation(ex, "Failed to read music video nfo metadata from {Path}", nfoFileName); |
|
_client.Notify(ex); |
|
return None; |
|
} |
|
} |
|
|
|
private async Task<Option<SongMetadata>> LoadSongMetadata(Song song, string ffprobePath) |
|
{ |
|
string path = song.GetHeadVersion().MediaFiles.Head().Path; |
|
|
|
try |
|
{ |
|
Either<BaseError, Dictionary<string, string>> maybeTags = |
|
await _localStatisticsProvider.GetFormatTags(ffprobePath, song); |
|
|
|
foreach (Dictionary<string, string> tags in maybeTags.RightToSeq()) |
|
{ |
|
Option<SongMetadata> maybeFallbackMetadata = |
|
_fallbackMetadataProvider.GetFallbackMetadata(song); |
|
|
|
var result = new SongMetadata |
|
{ |
|
MetadataKind = MetadataKind.Embedded, |
|
DateAdded = DateTime.UtcNow, |
|
DateUpdated = File.GetLastWriteTimeUtc(path), |
|
|
|
Artwork = new List<Artwork>(), |
|
Actors = new List<Actor>(), |
|
Genres = new List<Genre>(), |
|
Studios = new List<Studio>(), |
|
Tags = new List<Tag>() |
|
}; |
|
|
|
if (tags.TryGetValue(MetadataFormatTag.Album, out string album)) |
|
{ |
|
result.Album = album; |
|
} |
|
|
|
if (tags.TryGetValue(MetadataFormatTag.Artist, out string artist)) |
|
{ |
|
result.Artist = artist; |
|
} |
|
|
|
if (tags.TryGetValue(MetadataFormatTag.AlbumArtist, out string albumArtist)) |
|
{ |
|
result.AlbumArtist = albumArtist; |
|
} |
|
|
|
if (tags.TryGetValue(MetadataFormatTag.Date, out string date)) |
|
{ |
|
result.Date = date; |
|
} |
|
|
|
if (tags.TryGetValue(MetadataFormatTag.Genre, out string genre)) |
|
{ |
|
result.Genres.AddRange(SplitGenres(genre).Map(n => new Genre { Name = n })); |
|
} |
|
|
|
if (tags.TryGetValue(MetadataFormatTag.Title, out string title)) |
|
{ |
|
result.Title = title; |
|
} |
|
|
|
if (tags.TryGetValue(MetadataFormatTag.Track, out string track)) |
|
{ |
|
result.Track = track; |
|
} |
|
|
|
foreach (SongMetadata fallbackMetadata in maybeFallbackMetadata) |
|
{ |
|
if (string.IsNullOrWhiteSpace(result.Title)) |
|
{ |
|
result.Title = fallbackMetadata.Title; |
|
} |
|
|
|
result.OriginalTitle = fallbackMetadata.OriginalTitle; |
|
|
|
// preserve folder tagging - maybe someone uses this |
|
foreach (Tag tag in fallbackMetadata.Tags) |
|
{ |
|
result.Tags.Add(tag); |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
return Option<SongMetadata>.None; |
|
} |
|
catch (Exception ex) |
|
{ |
|
_logger.LogInformation(ex, "Failed to read embedded song metadata from {Path}", path); |
|
_client.Notify(ex); |
|
return None; |
|
} |
|
} |
|
|
|
private async Task<bool> ApplyMetadataUpdate(Episode episode, List<EpisodeMetadata> episodeMetadata) |
|
{ |
|
var updated = false; |
|
|
|
episode.EpisodeMetadata ??= new List<EpisodeMetadata>(); |
|
|
|
var toUpdate = episode.EpisodeMetadata |
|
.Where(em => episodeMetadata.Any(em2 => em2.EpisodeNumber == em.EpisodeNumber)) |
|
.ToList(); |
|
var toRemove = episode.EpisodeMetadata.Except(toUpdate).ToList(); |
|
var toAdd = episodeMetadata |
|
.Where(em => episode.EpisodeMetadata.All(em2 => em2.EpisodeNumber != em.EpisodeNumber)) |
|
.ToList(); |
|
|
|
foreach (EpisodeMetadata metadata in toRemove) |
|
{ |
|
await _televisionRepository.RemoveMetadata(episode, metadata); |
|
updated = true; |
|
} |
|
|
|
foreach (EpisodeMetadata metadata in toAdd) |
|
{ |
|
metadata.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle) |
|
? _fallbackMetadataProvider.GetSortTitle(metadata.Title) |
|
: metadata.SortTitle; |
|
metadata.EpisodeId = episode.Id; |
|
metadata.Episode = episode; |
|
episode.EpisodeMetadata.Add(metadata); |
|
|
|
updated = await _metadataRepository.Add(metadata) || updated; |
|
} |
|
|
|
foreach (EpisodeMetadata existing in toUpdate) |
|
{ |
|
Option<EpisodeMetadata> maybeIncoming = |
|
episodeMetadata.Find(em => em.EpisodeNumber == existing.EpisodeNumber); |
|
foreach (EpisodeMetadata metadata in maybeIncoming) |
|
{ |
|
existing.Outline = metadata.Outline; |
|
existing.Plot = metadata.Plot; |
|
existing.Tagline = metadata.Tagline; |
|
existing.Title = metadata.Title; |
|
|
|
if (existing.DateAdded == SystemTime.MinValueUtc) |
|
{ |
|
existing.DateAdded = metadata.DateAdded; |
|
} |
|
|
|
existing.DateUpdated = metadata.DateUpdated; |
|
existing.MetadataKind = metadata.MetadataKind; |
|
existing.OriginalTitle = metadata.OriginalTitle; |
|
existing.ReleaseDate = metadata.ReleaseDate; |
|
existing.Year = metadata.Year; |
|
existing.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle) |
|
? _fallbackMetadataProvider.GetSortTitle(metadata.Title) |
|
: metadata.SortTitle; |
|
|
|
updated = await UpdateMetadataCollections( |
|
existing, |
|
metadata, |
|
(_, _) => Task.FromResult(false), |
|
(_, _) => Task.FromResult(false), |
|
(_, _) => Task.FromResult(false), |
|
_televisionRepository.AddActor) || updated; |
|
|
|
foreach (Director director in existing.Directors |
|
.Filter(d => metadata.Directors.All(d2 => d2.Name != d.Name)).ToList()) |
|
{ |
|
existing.Directors.Remove(director); |
|
if (await _metadataRepository.RemoveDirector(director)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (Director director in metadata.Directors |
|
.Filter(d => existing.Directors.All(d2 => d2.Name != d.Name)).ToList()) |
|
{ |
|
existing.Directors.Add(director); |
|
if (await _televisionRepository.AddDirector(existing, director)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (Writer writer in existing.Writers |
|
.Filter(w => metadata.Writers.All(w2 => w2.Name != w.Name)).ToList()) |
|
{ |
|
existing.Writers.Remove(writer); |
|
if (await _metadataRepository.RemoveWriter(writer)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (Writer writer in metadata.Writers |
|
.Filter(w => existing.Writers.All(w2 => w2.Name != w.Name)).ToList()) |
|
{ |
|
existing.Writers.Add(writer); |
|
if (await _televisionRepository.AddWriter(existing, writer)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (MetadataGuid guid in existing.Guids |
|
.Filter(g => metadata.Guids.All(g2 => g2.Guid != g.Guid)).ToList()) |
|
{ |
|
existing.Guids.Remove(guid); |
|
if (await _metadataRepository.RemoveGuid(guid)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (MetadataGuid guid in metadata.Guids |
|
.Filter(g => existing.Guids.All(g2 => g2.Guid != g.Guid)).ToList()) |
|
{ |
|
existing.Guids.Add(guid); |
|
if (await _metadataRepository.AddGuid(existing, guid)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
return await _metadataRepository.Update(existing) || updated; |
|
} |
|
} |
|
|
|
return updated; |
|
} |
|
|
|
private async Task<bool> ApplyMetadataUpdate(Movie movie, MovieMetadata metadata) |
|
{ |
|
Option<MovieMetadata> maybeMetadata = Optional(movie.MovieMetadata).Flatten().HeadOrNone(); |
|
foreach (MovieMetadata existing in maybeMetadata) |
|
{ |
|
existing.ContentRating = metadata.ContentRating; |
|
existing.Outline = metadata.Outline; |
|
existing.Plot = metadata.Plot; |
|
existing.Tagline = metadata.Tagline; |
|
existing.Title = metadata.Title; |
|
|
|
if (existing.DateAdded == SystemTime.MinValueUtc) |
|
{ |
|
existing.DateAdded = metadata.DateAdded; |
|
} |
|
|
|
existing.DateUpdated = metadata.DateUpdated; |
|
existing.MetadataKind = metadata.MetadataKind; |
|
existing.OriginalTitle = metadata.OriginalTitle; |
|
existing.ReleaseDate = metadata.ReleaseDate; |
|
existing.Year = metadata.Year; |
|
existing.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle) |
|
? _fallbackMetadataProvider.GetSortTitle(metadata.Title) |
|
: metadata.SortTitle; |
|
|
|
bool updated = await UpdateMetadataCollections( |
|
existing, |
|
metadata, |
|
_movieRepository.AddGenre, |
|
_movieRepository.AddTag, |
|
_movieRepository.AddStudio, |
|
_movieRepository.AddActor); |
|
|
|
foreach (Director director in existing.Directors |
|
.Filter(d => metadata.Directors.All(d2 => d2.Name != d.Name)).ToList()) |
|
{ |
|
existing.Directors.Remove(director); |
|
if (await _metadataRepository.RemoveDirector(director)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (Director director in metadata.Directors |
|
.Filter(d => existing.Directors.All(d2 => d2.Name != d.Name)).ToList()) |
|
{ |
|
existing.Directors.Add(director); |
|
if (await _movieRepository.AddDirector(existing, director)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (Writer writer in existing.Writers |
|
.Filter(w => metadata.Writers.All(w2 => w2.Name != w.Name)).ToList()) |
|
{ |
|
existing.Writers.Remove(writer); |
|
if (await _metadataRepository.RemoveWriter(writer)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (Writer writer in metadata.Writers |
|
.Filter(w => existing.Writers.All(w2 => w2.Name != w.Name)).ToList()) |
|
{ |
|
existing.Writers.Add(writer); |
|
if (await _movieRepository.AddWriter(existing, writer)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (MetadataGuid guid in existing.Guids |
|
.Filter(g => metadata.Guids.All(g2 => g2.Guid != g.Guid)).ToList()) |
|
{ |
|
existing.Guids.Remove(guid); |
|
if (await _metadataRepository.RemoveGuid(guid)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (MetadataGuid guid in metadata.Guids |
|
.Filter(g => existing.Guids.All(g2 => g2.Guid != g.Guid)).ToList()) |
|
{ |
|
existing.Guids.Add(guid); |
|
if (await _metadataRepository.AddGuid(existing, guid)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
return await _metadataRepository.Update(existing) || updated; |
|
} |
|
|
|
metadata.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle) |
|
? _fallbackMetadataProvider.GetSortTitle(metadata.Title) |
|
: metadata.SortTitle; |
|
metadata.MovieId = movie.Id; |
|
movie.MovieMetadata = new List<MovieMetadata> { metadata }; |
|
|
|
return await _metadataRepository.Add(metadata); |
|
} |
|
|
|
private async Task<bool> ApplyMetadataUpdate(Show show, ShowMetadata metadata) |
|
{ |
|
Option<ShowMetadata> maybeMetadata = Optional(show.ShowMetadata).Flatten().HeadOrNone(); |
|
foreach (ShowMetadata existing in maybeMetadata) |
|
{ |
|
existing.ContentRating = metadata.ContentRating; |
|
existing.Outline = metadata.Outline; |
|
existing.Plot = metadata.Plot; |
|
existing.Tagline = metadata.Tagline; |
|
existing.Title = metadata.Title; |
|
|
|
if (existing.DateAdded == SystemTime.MinValueUtc) |
|
{ |
|
existing.DateAdded = metadata.DateAdded; |
|
} |
|
|
|
existing.DateUpdated = metadata.DateUpdated; |
|
existing.MetadataKind = metadata.MetadataKind; |
|
existing.OriginalTitle = metadata.OriginalTitle; |
|
existing.ReleaseDate = metadata.ReleaseDate; |
|
existing.Year = metadata.Year; |
|
existing.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle) |
|
? _fallbackMetadataProvider.GetSortTitle(metadata.Title) |
|
: metadata.SortTitle; |
|
|
|
bool updated = await UpdateMetadataCollections( |
|
existing, |
|
metadata, |
|
_televisionRepository.AddGenre, |
|
_televisionRepository.AddTag, |
|
_televisionRepository.AddStudio, |
|
_televisionRepository.AddActor); |
|
|
|
foreach (MetadataGuid guid in existing.Guids |
|
.Filter(g => metadata.Guids.All(g2 => g2.Guid != g.Guid)).ToList()) |
|
{ |
|
existing.Guids.Remove(guid); |
|
if (await _metadataRepository.RemoveGuid(guid)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (MetadataGuid guid in metadata.Guids |
|
.Filter(g => existing.Guids.All(g2 => g2.Guid != g.Guid)).ToList()) |
|
{ |
|
existing.Guids.Add(guid); |
|
if (await _metadataRepository.AddGuid(existing, guid)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
return await _metadataRepository.Update(existing) || updated; |
|
} |
|
|
|
metadata.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle) |
|
? _fallbackMetadataProvider.GetSortTitle(metadata.Title) |
|
: metadata.SortTitle; |
|
metadata.ShowId = show.Id; |
|
show.ShowMetadata = new List<ShowMetadata> { metadata }; |
|
|
|
return await _metadataRepository.Add(metadata); |
|
} |
|
|
|
private async Task<bool> ApplyMetadataUpdate(Artist artist, ArtistMetadata metadata) |
|
{ |
|
Option<ArtistMetadata> maybeMetadata = Optional(artist.ArtistMetadata).Flatten().HeadOrNone(); |
|
foreach (ArtistMetadata existing in maybeMetadata) |
|
{ |
|
existing.Title = metadata.Title; |
|
existing.Disambiguation = metadata.Disambiguation; |
|
existing.Biography = metadata.Biography; |
|
|
|
if (existing.DateAdded == SystemTime.MinValueUtc) |
|
{ |
|
existing.DateAdded = metadata.DateAdded; |
|
} |
|
|
|
existing.DateUpdated = metadata.DateUpdated; |
|
existing.MetadataKind = metadata.MetadataKind; |
|
existing.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle) |
|
? _fallbackMetadataProvider.GetSortTitle(metadata.Title) |
|
: metadata.SortTitle; |
|
|
|
var updated = false; |
|
|
|
foreach (Genre genre in existing.Genres.Filter(g => metadata.Genres.All(g2 => g2.Name != g.Name)) |
|
.ToList()) |
|
{ |
|
existing.Genres.Remove(genre); |
|
if (await _metadataRepository.RemoveGenre(genre)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (Genre genre in metadata.Genres.Filter(g => existing.Genres.All(g2 => g2.Name != g.Name)) |
|
.ToList()) |
|
{ |
|
existing.Genres.Add(genre); |
|
if (await _artistRepository.AddGenre(existing, genre)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (Style style in existing.Styles.Filter(s => metadata.Styles.All(s2 => s2.Name != s.Name)) |
|
.ToList()) |
|
{ |
|
existing.Styles.Remove(style); |
|
if (await _metadataRepository.RemoveStyle(style)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (Style style in metadata.Styles.Filter(s => existing.Styles.All(s2 => s2.Name != s.Name)) |
|
.ToList()) |
|
{ |
|
existing.Styles.Add(style); |
|
if (await _artistRepository.AddStyle(existing, style)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (Mood mood in existing.Moods.Filter(m => metadata.Moods.All(m2 => m2.Name != m.Name)) |
|
.ToList()) |
|
{ |
|
existing.Moods.Remove(mood); |
|
if (await _metadataRepository.RemoveMood(mood)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (Mood mood in metadata.Moods.Filter(s => existing.Moods.All(m2 => m2.Name != s.Name)) |
|
.ToList()) |
|
{ |
|
existing.Moods.Add(mood); |
|
if (await _artistRepository.AddMood(existing, mood)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
return await _metadataRepository.Update(existing) || updated; |
|
} |
|
|
|
metadata.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle) |
|
? _fallbackMetadataProvider.GetSortTitle(metadata.Title) |
|
: metadata.SortTitle; |
|
metadata.ArtistId = artist.Id; |
|
artist.ArtistMetadata = new List<ArtistMetadata> { metadata }; |
|
|
|
return await _metadataRepository.Add(metadata); |
|
} |
|
|
|
private async Task<bool> ApplyMetadataUpdate(MusicVideo musicVideo, MusicVideoMetadata metadata) |
|
{ |
|
Option<MusicVideoMetadata> maybeMetadata = Optional(musicVideo.MusicVideoMetadata).Flatten().HeadOrNone(); |
|
foreach (MusicVideoMetadata existing in maybeMetadata) |
|
{ |
|
existing.Title = metadata.Title; |
|
existing.Year = metadata.Year; |
|
existing.Plot = metadata.Plot; |
|
existing.Track = metadata.Track; |
|
existing.Album = metadata.Album; |
|
|
|
if (existing.DateAdded == SystemTime.MinValueUtc) |
|
{ |
|
existing.DateAdded = metadata.DateAdded; |
|
} |
|
|
|
existing.DateUpdated = metadata.DateUpdated; |
|
existing.MetadataKind = metadata.MetadataKind; |
|
existing.OriginalTitle = metadata.OriginalTitle; |
|
existing.ReleaseDate = metadata.ReleaseDate; |
|
existing.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle) |
|
? _fallbackMetadataProvider.GetSortTitle(metadata.Title) |
|
: metadata.SortTitle; |
|
|
|
bool updated = await UpdateMetadataCollections( |
|
existing, |
|
metadata, |
|
_musicVideoRepository.AddGenre, |
|
_musicVideoRepository.AddTag, |
|
_musicVideoRepository.AddStudio, |
|
(_, _) => Task.FromResult(false)); |
|
|
|
foreach (MusicVideoArtist artist in existing.Artists |
|
.Filter(g => metadata.Artists.All(g2 => g2.Name != g.Name)) |
|
.ToList()) |
|
{ |
|
existing.Artists.Remove(artist); |
|
if (await _musicVideoRepository.RemoveArtist(artist)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (MusicVideoArtist artist in metadata.Artists |
|
.Filter(g => existing.Artists.All(g2 => g2.Name != g.Name)) |
|
.ToList()) |
|
{ |
|
existing.Artists.Add(artist); |
|
if (await _musicVideoRepository.AddArtist(existing, artist)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
return await _metadataRepository.Update(existing) || updated; |
|
} |
|
|
|
metadata.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle) |
|
? _fallbackMetadataProvider.GetSortTitle(metadata.Title) |
|
: metadata.SortTitle; |
|
metadata.MusicVideoId = musicVideo.Id; |
|
musicVideo.MusicVideoMetadata = new List<MusicVideoMetadata> { metadata }; |
|
|
|
return await _metadataRepository.Add(metadata); |
|
} |
|
|
|
private async Task<bool> ApplyMetadataUpdate(OtherVideo otherVideo, OtherVideoMetadata metadata) |
|
{ |
|
Option<OtherVideoMetadata> maybeMetadata = Optional(otherVideo.OtherVideoMetadata).Flatten().HeadOrNone(); |
|
foreach (OtherVideoMetadata existing in maybeMetadata) |
|
{ |
|
existing.ContentRating = metadata.ContentRating; |
|
existing.Outline = metadata.Outline; |
|
existing.Plot = metadata.Plot; |
|
existing.Tagline = metadata.Tagline; |
|
existing.Title = metadata.Title; |
|
|
|
if (existing.DateAdded == SystemTime.MinValueUtc) |
|
{ |
|
existing.DateAdded = metadata.DateAdded; |
|
} |
|
|
|
existing.DateUpdated = metadata.DateUpdated; |
|
existing.MetadataKind = metadata.MetadataKind; |
|
existing.OriginalTitle = metadata.OriginalTitle; |
|
existing.ReleaseDate = metadata.ReleaseDate; |
|
existing.Year = metadata.Year; |
|
existing.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle) |
|
? _fallbackMetadataProvider.GetSortTitle(metadata.Title) |
|
: metadata.SortTitle; |
|
existing.OriginalTitle = metadata.OriginalTitle; |
|
|
|
bool updated = await UpdateMetadataCollections( |
|
existing, |
|
metadata, |
|
_otherVideoRepository.AddGenre, |
|
_otherVideoRepository.AddTag, |
|
_otherVideoRepository.AddStudio, |
|
_otherVideoRepository.AddActor); |
|
|
|
foreach (Director director in existing.Directors |
|
.Filter(d => metadata.Directors.All(d2 => d2.Name != d.Name)).ToList()) |
|
{ |
|
existing.Directors.Remove(director); |
|
if (await _metadataRepository.RemoveDirector(director)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (Director director in metadata.Directors |
|
.Filter(d => existing.Directors.All(d2 => d2.Name != d.Name)).ToList()) |
|
{ |
|
existing.Directors.Add(director); |
|
if (await _otherVideoRepository.AddDirector(existing, director)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (Writer writer in existing.Writers |
|
.Filter(w => metadata.Writers.All(w2 => w2.Name != w.Name)).ToList()) |
|
{ |
|
existing.Writers.Remove(writer); |
|
if (await _metadataRepository.RemoveWriter(writer)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (Writer writer in metadata.Writers |
|
.Filter(w => existing.Writers.All(w2 => w2.Name != w.Name)).ToList()) |
|
{ |
|
existing.Writers.Add(writer); |
|
if (await _otherVideoRepository.AddWriter(existing, writer)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (MetadataGuid guid in existing.Guids |
|
.Filter(g => metadata.Guids.All(g2 => g2.Guid != g.Guid)).ToList()) |
|
{ |
|
existing.Guids.Remove(guid); |
|
if (await _metadataRepository.RemoveGuid(guid)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (MetadataGuid guid in metadata.Guids |
|
.Filter(g => existing.Guids.All(g2 => g2.Guid != g.Guid)).ToList()) |
|
{ |
|
existing.Guids.Add(guid); |
|
if (await _metadataRepository.AddGuid(existing, guid)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
return await _metadataRepository.Update(existing) || updated; |
|
} |
|
|
|
metadata.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle) |
|
? _fallbackMetadataProvider.GetSortTitle(metadata.Title) |
|
: metadata.SortTitle; |
|
metadata.OtherVideoId = otherVideo.Id; |
|
otherVideo.OtherVideoMetadata = new List<OtherVideoMetadata> { metadata }; |
|
|
|
return await _metadataRepository.Add(metadata); |
|
} |
|
|
|
private async Task<bool> ApplyMetadataUpdate(Song song, SongMetadata metadata) |
|
{ |
|
Option<SongMetadata> maybeMetadata = Optional(song.SongMetadata).Flatten().HeadOrNone(); |
|
foreach (SongMetadata existing in maybeMetadata) |
|
{ |
|
existing.Title = metadata.Title; |
|
existing.Artist = metadata.Artist; |
|
existing.AlbumArtist = metadata.AlbumArtist; |
|
existing.Album = metadata.Album; |
|
existing.Date = metadata.Date; |
|
existing.Track = metadata.Track; |
|
|
|
if (existing.DateAdded == SystemTime.MinValueUtc) |
|
{ |
|
existing.DateAdded = metadata.DateAdded; |
|
} |
|
|
|
existing.DateUpdated = metadata.DateUpdated; |
|
existing.MetadataKind = metadata.MetadataKind; |
|
existing.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle) |
|
? _fallbackMetadataProvider.GetSortTitle(metadata.Title) |
|
: metadata.SortTitle; |
|
existing.OriginalTitle = metadata.OriginalTitle; |
|
|
|
bool updated = await UpdateMetadataCollections( |
|
existing, |
|
metadata, |
|
_songRepository.AddGenre, |
|
_songRepository.AddTag, |
|
(_, _) => Task.FromResult(false), |
|
(_, _) => Task.FromResult(false)); |
|
|
|
return await _metadataRepository.Update(existing) || updated; |
|
} |
|
|
|
metadata.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle) |
|
? _fallbackMetadataProvider.GetSortTitle(metadata.Title) |
|
: metadata.SortTitle; |
|
metadata.SongId = song.Id; |
|
song.SongMetadata = new List<SongMetadata> { metadata }; |
|
|
|
return await _metadataRepository.Add(metadata); |
|
} |
|
|
|
private async Task<Option<ShowMetadata>> LoadTelevisionShowMetadata(string nfoFileName) |
|
{ |
|
try |
|
{ |
|
Either<BaseError, TvShowNfo> maybeNfo = await _tvShowNfoReader.ReadFromFile(nfoFileName); |
|
foreach (BaseError error in maybeNfo.LeftToSeq()) |
|
{ |
|
_logger.LogInformation( |
|
"Failed to read TvShow nfo metadata from {Path}: {Error}", |
|
nfoFileName, |
|
error.ToString()); |
|
} |
|
|
|
foreach (TvShowNfo nfo in maybeNfo.RightToSeq()) |
|
{ |
|
DateTime dateAdded = DateTime.UtcNow; |
|
DateTime dateUpdated = File.GetLastWriteTimeUtc(nfoFileName); |
|
|
|
return new ShowMetadata |
|
{ |
|
MetadataKind = MetadataKind.Sidecar, |
|
DateAdded = dateAdded, |
|
DateUpdated = dateUpdated, |
|
Title = nfo.Title, |
|
Plot = nfo.Plot, |
|
Outline = nfo.Outline, |
|
Tagline = nfo.Tagline, |
|
ContentRating = nfo.ContentRating, |
|
Year = GetYear(nfo.Year, nfo.Premiered), |
|
ReleaseDate = GetAired(nfo.Year, nfo.Premiered), |
|
Genres = nfo.Genres.Map(g => new Genre { Name = g }).ToList(), |
|
Tags = nfo.Tags.Map(t => new Tag { Name = t }).ToList(), |
|
Studios = nfo.Studios.Map(s => new Studio { Name = s }).ToList(), |
|
Actors = Actors(nfo.Actors, dateAdded, dateUpdated), |
|
Guids = nfo.UniqueIds |
|
.Map(id => new MetadataGuid { Guid = $"{id.Type}://{id.Guid}" }) |
|
.ToList() |
|
}; |
|
} |
|
|
|
return None; |
|
} |
|
catch (Exception ex) |
|
{ |
|
_logger.LogInformation(ex, "Failed to read TV show nfo metadata from {Path}", nfoFileName); |
|
_client.Notify(ex); |
|
return None; |
|
} |
|
} |
|
|
|
private async Task<Option<ArtistMetadata>> LoadArtistMetadata(string nfoFileName) |
|
{ |
|
try |
|
{ |
|
Either<BaseError, ArtistNfo> maybeNfo = await _artistNfoReader.ReadFromFile(nfoFileName); |
|
foreach (BaseError error in maybeNfo.LeftToSeq()) |
|
{ |
|
_logger.LogInformation( |
|
"Failed to read Artist nfo metadata from {Path}: {Error}", |
|
nfoFileName, |
|
error.ToString()); |
|
} |
|
|
|
foreach (ArtistNfo nfo in maybeNfo.RightToSeq()) |
|
{ |
|
return new ArtistMetadata |
|
{ |
|
MetadataKind = MetadataKind.Sidecar, |
|
DateAdded = DateTime.UtcNow, |
|
DateUpdated = File.GetLastWriteTimeUtc(nfoFileName), |
|
Title = nfo.Name, |
|
Disambiguation = nfo.Disambiguation, |
|
Biography = nfo.Biography, |
|
Genres = nfo.Genres.Map(g => new Genre { Name = g }).ToList(), |
|
Styles = nfo.Styles.Map(s => new Style { Name = s }).ToList(), |
|
Moods = nfo.Moods.Map(m => new Mood { Name = m }).ToList() |
|
}; |
|
} |
|
|
|
return None; |
|
} |
|
catch (Exception ex) |
|
{ |
|
_logger.LogInformation(ex, "Failed to read artist nfo metadata from {Path}", nfoFileName); |
|
_client.Notify(ex); |
|
return None; |
|
} |
|
} |
|
|
|
private async Task<List<EpisodeMetadata>> LoadEpisodeMetadata(Episode episode, string nfoFileName) |
|
{ |
|
try |
|
{ |
|
Either<BaseError, List<TvShowEpisodeNfo>> maybeNfo = await _episodeNfoReader.ReadFromFile(nfoFileName); |
|
foreach (BaseError error in maybeNfo.LeftToSeq()) |
|
{ |
|
_logger.LogInformation( |
|
"Failed to read Episode nfo metadata from {Path}: {Error}", |
|
nfoFileName, |
|
error.ToString()); |
|
} |
|
|
|
var result = new List<EpisodeMetadata>(); |
|
foreach (TvShowEpisodeNfo nfo in maybeNfo.RightToSeq().Flatten()) |
|
{ |
|
DateTime dateAdded = DateTime.UtcNow; |
|
DateTime dateUpdated = File.GetLastWriteTimeUtc(nfoFileName); |
|
|
|
var metadata = new EpisodeMetadata |
|
{ |
|
MetadataKind = MetadataKind.Sidecar, |
|
DateAdded = dateAdded, |
|
DateUpdated = dateUpdated, |
|
Title = nfo.Title, |
|
SortTitle = _fallbackMetadataProvider.GetSortTitle(nfo.Title), |
|
EpisodeNumber = nfo.Episode, |
|
Year = GetYear(0, nfo.Aired), |
|
ReleaseDate = GetAired(0, nfo.Aired), |
|
Plot = nfo.Plot, |
|
Actors = Actors(nfo.Actors, dateAdded, dateUpdated), |
|
Guids = nfo.UniqueIds |
|
.Map(id => new MetadataGuid { Guid = $"{id.Type}://{id.Guid}" }) |
|
.ToList(), |
|
Directors = nfo.Directors.Map(d => new Director { Name = d }).ToList(), |
|
Writers = nfo.Writers.Map(w => new Writer { Name = w }).ToList(), |
|
Genres = new List<Genre>(), |
|
Tags = new List<Tag>(), |
|
Studios = new List<Studio>(), |
|
Artwork = new List<Artwork>() |
|
}; |
|
|
|
result.Add(metadata); |
|
} |
|
|
|
return result; |
|
} |
|
catch (Exception ex) |
|
{ |
|
_logger.LogInformation(ex, "Failed to read TV episode nfo metadata from {Path}", nfoFileName); |
|
_client.Notify(ex); |
|
return _fallbackMetadataProvider.GetFallbackMetadata(episode); |
|
} |
|
} |
|
|
|
private async Task<Option<MovieMetadata>> LoadMovieMetadata(Movie movie, string nfoFileName) |
|
{ |
|
try |
|
{ |
|
Either<BaseError, MovieNfo> maybeNfo = await _movieNfoReader.ReadFromFile(nfoFileName); |
|
foreach (BaseError error in maybeNfo.LeftToSeq()) |
|
{ |
|
_logger.LogInformation( |
|
"Failed to read Movie nfo metadata from {Path}: {Error}", |
|
nfoFileName, |
|
error.ToString()); |
|
|
|
return _fallbackMetadataProvider.GetFallbackMetadata(movie); |
|
} |
|
|
|
foreach (MovieNfo nfo in maybeNfo.RightToSeq()) |
|
{ |
|
DateTime dateAdded = DateTime.UtcNow; |
|
DateTime dateUpdated = File.GetLastWriteTimeUtc(nfoFileName); |
|
|
|
var year = 0; |
|
if (nfo.Year > 1000) |
|
{ |
|
year = nfo.Year; |
|
} |
|
|
|
DateTime releaseDate = year > 0 |
|
? new DateTimeOffset(year, 1, 1, 0, 0, 0, TimeSpan.Zero).UtcDateTime |
|
: SystemTime.MinValueUtc; |
|
|
|
foreach (DateTime premiered in nfo.Premiered) |
|
{ |
|
if (year == 0) |
|
{ |
|
year = premiered.Year; |
|
} |
|
|
|
releaseDate = premiered; |
|
} |
|
|
|
return new MovieMetadata |
|
{ |
|
MetadataKind = MetadataKind.Sidecar, |
|
DateAdded = dateAdded, |
|
DateUpdated = dateUpdated, |
|
Title = nfo.Title, |
|
SortTitle = nfo.SortTitle, |
|
Year = year, |
|
ContentRating = nfo.ContentRating, |
|
ReleaseDate = releaseDate, |
|
Plot = nfo.Plot, |
|
Outline = nfo.Outline, |
|
Tagline = nfo.Tagline, |
|
Genres = nfo.Genres.Map(g => new Genre { Name = g }).ToList(), |
|
Tags = nfo.Tags.Map(t => new Tag { Name = t }).ToList(), |
|
Studios = nfo.Studios.Map(s => new Studio { Name = s }).ToList(), |
|
Actors = Actors(nfo.Actors, dateAdded, dateUpdated), |
|
Directors = nfo.Directors.Map(d => new Director { Name = d }).ToList(), |
|
Writers = nfo.Writers.Map(w => new Writer { Name = w }).ToList(), |
|
Guids = nfo.UniqueIds |
|
.Map(id => new MetadataGuid { Guid = $"{id.Type}://{id.Guid}" }) |
|
.ToList() |
|
}; |
|
} |
|
|
|
return None; |
|
} |
|
catch (Exception ex) |
|
{ |
|
_logger.LogInformation(ex, "Failed to read Movie nfo metadata from {Path}", nfoFileName); |
|
_client.Notify(ex); |
|
return _fallbackMetadataProvider.GetFallbackMetadata(movie); |
|
} |
|
} |
|
|
|
private async Task<Option<OtherVideoMetadata>> LoadOtherVideoMetadata(string nfoFileName) |
|
{ |
|
try |
|
{ |
|
Either<BaseError, OtherVideoNfo> maybeNfo = await _otherVideoNfoReader.ReadFromFile(nfoFileName); |
|
foreach (BaseError error in maybeNfo.LeftToSeq()) |
|
{ |
|
_logger.LogInformation( |
|
"Failed to read OtherVideo nfo metadata from {Path}: {Error}", |
|
nfoFileName, |
|
error.ToString()); |
|
|
|
return None; |
|
} |
|
|
|
foreach (OtherVideoNfo nfo in maybeNfo.RightToSeq()) |
|
{ |
|
DateTime dateAdded = DateTime.UtcNow; |
|
DateTime dateUpdated = File.GetLastWriteTimeUtc(nfoFileName); |
|
|
|
var year = 0; |
|
if (nfo.Year > 1000) |
|
{ |
|
year = nfo.Year; |
|
} |
|
|
|
DateTime releaseDate = year > 0 |
|
? new DateTimeOffset(year, 1, 1, 0, 0, 0, TimeSpan.Zero).UtcDateTime |
|
: SystemTime.MinValueUtc; |
|
|
|
foreach (DateTime premiered in nfo.Premiered) |
|
{ |
|
if (year == 0) |
|
{ |
|
year = premiered.Year; |
|
} |
|
|
|
releaseDate = premiered; |
|
} |
|
|
|
return new OtherVideoMetadata |
|
{ |
|
MetadataKind = MetadataKind.Sidecar, |
|
DateAdded = dateAdded, |
|
DateUpdated = dateUpdated, |
|
Title = nfo.Title, |
|
SortTitle = nfo.SortTitle, |
|
Year = year, |
|
ContentRating = nfo.ContentRating, |
|
ReleaseDate = releaseDate, |
|
Plot = nfo.Plot, |
|
Outline = nfo.Outline, |
|
Tagline = nfo.Tagline, |
|
Genres = nfo.Genres.Map(g => new Genre { Name = g }).ToList(), |
|
Tags = nfo.Tags.Map(t => new Tag { Name = t }).ToList(), |
|
Studios = nfo.Studios.Map(s => new Studio { Name = s }).ToList(), |
|
Actors = Actors(nfo.Actors, dateAdded, dateUpdated), |
|
Directors = nfo.Directors.Map(d => new Director { Name = d }).ToList(), |
|
Writers = nfo.Writers.Map(w => new Writer { Name = w }).ToList(), |
|
Guids = nfo.UniqueIds |
|
.Map(id => new MetadataGuid { Guid = $"{id.Type}://{id.Guid}" }) |
|
.ToList() |
|
}; |
|
} |
|
} |
|
catch (Exception ex) |
|
{ |
|
_logger.LogInformation(ex, "Failed to read OtherVideo nfo metadata from {Path}", nfoFileName); |
|
_client.Notify(ex); |
|
} |
|
|
|
return None; |
|
} |
|
|
|
private static int? GetYear(int? year, Option<DateTime> premiered) |
|
{ |
|
if (year is > 1000) |
|
{ |
|
return year; |
|
} |
|
|
|
foreach (DateTime p in premiered) |
|
{ |
|
return p.Year; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
private static DateTime? GetAired(int? year, Option<DateTime> aired) |
|
{ |
|
DateTime? fallback = year is > 1000 ? new DateTime(year.Value, 1, 1) : null; |
|
|
|
foreach (DateTime a in aired) |
|
{ |
|
return a; |
|
} |
|
|
|
return fallback; |
|
} |
|
|
|
private async Task<bool> UpdateMetadataCollections<T>( |
|
T existing, |
|
T incoming, |
|
Func<T, Genre, Task<bool>> addGenre, |
|
Func<T, Tag, Task<bool>> addTag, |
|
Func<T, Studio, Task<bool>> addStudio, |
|
Func<T, Actor, Task<bool>> addActor) |
|
where T : Domain.Metadata |
|
{ |
|
var updated = false; |
|
|
|
if (existing is not EpisodeMetadata) |
|
{ |
|
foreach (Genre genre in existing.Genres.Filter(g => incoming.Genres.All(g2 => g2.Name != g.Name)) |
|
.ToList()) |
|
{ |
|
existing.Genres.Remove(genre); |
|
if (await _metadataRepository.RemoveGenre(genre)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (Genre genre in incoming.Genres.Filter(g => existing.Genres.All(g2 => g2.Name != g.Name)) |
|
.ToList()) |
|
{ |
|
existing.Genres.Add(genre); |
|
if (await addGenre(existing, genre)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (Tag tag in existing.Tags.Filter(t => incoming.Tags.All(t2 => t2.Name != t.Name)) |
|
.ToList()) |
|
{ |
|
existing.Tags.Remove(tag); |
|
if (await _metadataRepository.RemoveTag(tag)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (Tag tag in incoming.Tags.Filter(t => existing.Tags.All(t2 => t2.Name != t.Name)) |
|
.ToList()) |
|
{ |
|
existing.Tags.Add(tag); |
|
if (await addTag(existing, tag)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (Studio studio in existing.Studios |
|
.Filter(s => incoming.Studios.All(s2 => s2.Name != s.Name)) |
|
.ToList()) |
|
{ |
|
existing.Studios.Remove(studio); |
|
if (await _metadataRepository.RemoveStudio(studio)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (Studio studio in incoming.Studios |
|
.Filter(s => existing.Studios.All(s2 => s2.Name != s.Name)) |
|
.ToList()) |
|
{ |
|
existing.Studios.Add(studio); |
|
if (await addStudio(existing, studio)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
} |
|
|
|
if (existing is not MusicVideoMetadata and not SongMetadata) |
|
{ |
|
foreach (Actor actor in existing.Actors |
|
.Filter(a => incoming.Actors.All(a2 => a2.Name != a.Name)) |
|
.ToList()) |
|
{ |
|
existing.Actors.Remove(actor); |
|
if (await _metadataRepository.RemoveActor(actor)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
|
|
foreach (Actor actor in incoming.Actors |
|
.Filter(a => existing.Actors.All(a2 => a2.Name != a.Name)) |
|
.ToList()) |
|
{ |
|
existing.Actors.Add(actor); |
|
if (await addActor(existing, actor)) |
|
{ |
|
updated = true; |
|
} |
|
} |
|
} |
|
|
|
return updated; |
|
} |
|
|
|
private static List<Actor> Actors(List<ActorNfo> actorNfos, DateTime dateAdded, DateTime dateUpdated) |
|
{ |
|
var result = new List<Actor>(); |
|
|
|
for (var i = 0; i < actorNfos.Count; i++) |
|
{ |
|
ActorNfo actorNfo = actorNfos[i]; |
|
|
|
var actor = new Actor |
|
{ |
|
Name = actorNfo.Name, |
|
Role = actorNfo.Role, |
|
Order = actorNfo.Order ?? i |
|
}; |
|
|
|
if (!string.IsNullOrWhiteSpace(actorNfo.Thumb)) |
|
{ |
|
actor.Artwork = new Artwork |
|
{ |
|
Path = actorNfo.Thumb, |
|
ArtworkKind = ArtworkKind.Thumbnail, |
|
DateAdded = dateAdded, |
|
DateUpdated = dateUpdated |
|
}; |
|
} |
|
|
|
result.Add(actor); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
private static IEnumerable<string> SplitGenres(string genre) |
|
{ |
|
char[] delimiters = new[] { '/', '|', ';', '\\' } |
|
.Filter(d => genre.IndexOf(d, StringComparison.OrdinalIgnoreCase) != -1) |
|
.DefaultIfEmpty(',') |
|
.ToArray(); |
|
|
|
return genre.Split(delimiters, StringSplitOptions.RemoveEmptyEntries) |
|
.Where(i => !string.IsNullOrWhiteSpace(i)) |
|
.Select(i => i.Trim()); |
|
} |
|
}
|
|
|