From 237729e79d0195d4d05fe8ffda019e831e26428a Mon Sep 17 00:00:00 2001 From: Jason Dove Date: Sat, 10 Apr 2021 20:40:54 -0500 Subject: [PATCH] add movie, show, artist language buttons. search by english language name (#162) --- .../Artists/ArtistViewModel.cs | 4 +- ErsatzTV.Application/Artists/Mapper.cs | 25 ++++++++-- .../Artists/Queries/GetArtistByIdHandler.cs | 26 ++++++++-- .../Queries/GetAllLanguageCodesHandler.cs | 2 +- ErsatzTV.Application/Movies/Mapper.cs | 25 +++++++++- ErsatzTV.Application/Movies/MovieViewModel.cs | 4 +- ErsatzTV.Application/Television/Mapper.cs | 24 +++++++-- .../Queries/GetTelevisionShowByIdHandler.cs | 28 ++++++++--- .../Television/TelevisionShowViewModel.cs | 4 +- .../Data/Repositories/MovieRepository.cs | 2 + ErsatzTV.Infrastructure/Search/SearchIndex.cs | 50 +++++++++++++++---- ErsatzTV/Pages/Artist.razor | 11 ++++ ErsatzTV/Pages/Movie.razor | 11 ++++ ErsatzTV/Pages/TelevisionSeasonList.razor | 11 ++++ 14 files changed, 195 insertions(+), 32 deletions(-) diff --git a/ErsatzTV.Application/Artists/ArtistViewModel.cs b/ErsatzTV.Application/Artists/ArtistViewModel.cs index 941fae8c2..0b37fc210 100644 --- a/ErsatzTV.Application/Artists/ArtistViewModel.cs +++ b/ErsatzTV.Application/Artists/ArtistViewModel.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Globalization; namespace ErsatzTV.Application.Artists { @@ -10,5 +11,6 @@ namespace ErsatzTV.Application.Artists string FanArt, List Genres, List Styles, - List Moods); + List Moods, + List Languages); } diff --git a/ErsatzTV.Application/Artists/Mapper.cs b/ErsatzTV.Application/Artists/Mapper.cs index ea3c1387c..45f635c49 100644 --- a/ErsatzTV.Application/Artists/Mapper.cs +++ b/ErsatzTV.Application/Artists/Mapper.cs @@ -1,12 +1,16 @@ -using System.Linq; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; using ErsatzTV.Core.Domain; +using LanguageExt; using static LanguageExt.Prelude; namespace ErsatzTV.Application.Artists { internal static class Mapper { - internal static ArtistViewModel ProjectToViewModel(Artist artist) + internal static ArtistViewModel ProjectToViewModel(Artist artist, List languages) { ArtistMetadata metadata = Optional(artist.ArtistMetadata).Flatten().Head(); return new ArtistViewModel( @@ -17,11 +21,26 @@ namespace ErsatzTV.Application.Artists Artwork(metadata, ArtworkKind.FanArt), metadata.Genres.Map(g => g.Name).ToList(), metadata.Styles.Map(s => s.Name).ToList(), - metadata.Moods.Map(m => m.Name).ToList()); + metadata.Moods.Map(m => m.Name).ToList(), + LanguagesForArtist(languages)); } private static string Artwork(Metadata metadata, ArtworkKind artworkKind) => Optional(metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == artworkKind)) .Match(a => a.Path, string.Empty); + + private static List LanguagesForArtist(List languages) + { + CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures); + + return languages + .Distinct() + .Map( + lang => allCultures.Filter( + ci => string.Equals(ci.ThreeLetterISOLanguageName, lang, StringComparison.OrdinalIgnoreCase))) + .Sequence() + .Flatten() + .ToList(); + } } } diff --git a/ErsatzTV.Application/Artists/Queries/GetArtistByIdHandler.cs b/ErsatzTV.Application/Artists/Queries/GetArtistByIdHandler.cs index 73205bac9..1dd3d6504 100644 --- a/ErsatzTV.Application/Artists/Queries/GetArtistByIdHandler.cs +++ b/ErsatzTV.Application/Artists/Queries/GetArtistByIdHandler.cs @@ -1,5 +1,7 @@ -using System.Threading; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; +using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Repositories; using LanguageExt; using MediatR; @@ -10,12 +12,26 @@ namespace ErsatzTV.Application.Artists.Queries public class GetArtistByIdHandler : IRequestHandler> { private readonly IArtistRepository _artistRepository; + private readonly ISearchRepository _searchRepository; - public GetArtistByIdHandler(IArtistRepository artistRepository) => _artistRepository = artistRepository; + public GetArtistByIdHandler(IArtistRepository artistRepository, ISearchRepository searchRepository) + { + _artistRepository = artistRepository; + _searchRepository = searchRepository; + } - public Task> Handle( + public async Task> Handle( GetArtistById request, - CancellationToken cancellationToken) => - _artistRepository.GetArtist(request.ArtistId).MapT(ProjectToViewModel); + CancellationToken cancellationToken) + { + Option maybeArtist = await _artistRepository.GetArtist(request.ArtistId); + return await maybeArtist.Match>>( + async artist => + { + List languages = await _searchRepository.GetLanguagesForArtist(artist); + return ProjectToViewModel(artist, languages); + }, + () => Task.FromResult(Option.None)); + } } } diff --git a/ErsatzTV.Application/MediaItems/Queries/GetAllLanguageCodesHandler.cs b/ErsatzTV.Application/MediaItems/Queries/GetAllLanguageCodesHandler.cs index 0202751b1..40f492951 100644 --- a/ErsatzTV.Application/MediaItems/Queries/GetAllLanguageCodesHandler.cs +++ b/ErsatzTV.Application/MediaItems/Queries/GetAllLanguageCodesHandler.cs @@ -20,7 +20,7 @@ namespace ErsatzTV.Application.MediaItems.Queries { var result = new List(); - CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.AllCultures); + CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures); List allLanguageCodes = await _mediaItemRepository.GetAllLanguageCodes(); foreach (string code in allLanguageCodes) { diff --git a/ErsatzTV.Application/Movies/Mapper.cs b/ErsatzTV.Application/Movies/Mapper.cs index 77d30c6e7..ca770e50a 100644 --- a/ErsatzTV.Application/Movies/Mapper.cs +++ b/ErsatzTV.Application/Movies/Mapper.cs @@ -1,5 +1,9 @@ -using System.Linq; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; using ErsatzTV.Core.Domain; +using LanguageExt; using static LanguageExt.Prelude; namespace ErsatzTV.Application.Movies @@ -17,7 +21,24 @@ namespace ErsatzTV.Application.Movies Artwork(metadata, ArtworkKind.FanArt), metadata.Genres.Map(g => g.Name).ToList(), metadata.Tags.Map(t => t.Name).ToList(), - metadata.Studios.Map(s => s.Name).ToList()); + metadata.Studios.Map(s => s.Name).ToList(), + LanguagesForMovie(movie)); + } + + private static List LanguagesForMovie(Movie movie) + { + CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures); + + return movie.MediaVersions + .Map(mv => mv.Streams.Filter(s => s.MediaStreamKind == MediaStreamKind.Audio).Map(s => s.Language)) + .Flatten() + .Distinct() + .Map( + lang => allCultures.Filter( + ci => string.Equals(ci.ThreeLetterISOLanguageName, lang, StringComparison.OrdinalIgnoreCase))) + .Sequence() + .Flatten() + .ToList(); } private static string Artwork(Metadata metadata, ArtworkKind artworkKind) => diff --git a/ErsatzTV.Application/Movies/MovieViewModel.cs b/ErsatzTV.Application/Movies/MovieViewModel.cs index 21bede862..9de16f26c 100644 --- a/ErsatzTV.Application/Movies/MovieViewModel.cs +++ b/ErsatzTV.Application/Movies/MovieViewModel.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Globalization; namespace ErsatzTV.Application.Movies { @@ -10,5 +11,6 @@ namespace ErsatzTV.Application.Movies string FanArt, List Genres, List Tags, - List Studios); + List Studios, + List Languages); } diff --git a/ErsatzTV.Application/Television/Mapper.cs b/ErsatzTV.Application/Television/Mapper.cs index 3cb9745bd..4edc7ac49 100644 --- a/ErsatzTV.Application/Television/Mapper.cs +++ b/ErsatzTV.Application/Television/Mapper.cs @@ -1,13 +1,16 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Globalization; using System.Linq; using ErsatzTV.Core.Domain; +using LanguageExt; using static LanguageExt.Prelude; namespace ErsatzTV.Application.Television { internal static class Mapper { - internal static TelevisionShowViewModel ProjectToViewModel(Show show) => + internal static TelevisionShowViewModel ProjectToViewModel(Show show, List languages) => new( show.Id, show.ShowMetadata.HeadOrNone().Map(m => m.Title ?? string.Empty).IfNone(string.Empty), @@ -18,7 +21,8 @@ namespace ErsatzTV.Application.Television show.ShowMetadata.HeadOrNone().Map(m => m.Genres.Map(g => g.Name).ToList()).IfNone(new List()), show.ShowMetadata.HeadOrNone().Map(m => m.Tags.Map(g => g.Name).ToList()).IfNone(new List()), show.ShowMetadata.HeadOrNone().Map(m => m.Studios.Map(s => s.Name).ToList()) - .IfNone(new List())); + .IfNone(new List()), + LanguagesForShow(languages)); internal static TelevisionSeasonViewModel ProjectToViewModel(Season season) => new( @@ -48,5 +52,19 @@ namespace ErsatzTV.Application.Television private static string GetArtwork(Metadata metadata, ArtworkKind artworkKind) => Optional(metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == artworkKind)) .Match(a => a.Path, string.Empty); + + private static List LanguagesForShow(List languages) + { + CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures); + + return languages + .Distinct() + .Map( + lang => allCultures.Filter( + ci => string.Equals(ci.ThreeLetterISOLanguageName, lang, StringComparison.OrdinalIgnoreCase))) + .Sequence() + .Flatten() + .ToList(); + } } } diff --git a/ErsatzTV.Application/Television/Queries/GetTelevisionShowByIdHandler.cs b/ErsatzTV.Application/Television/Queries/GetTelevisionShowByIdHandler.cs index 04ba479bb..8eee1ffda 100644 --- a/ErsatzTV.Application/Television/Queries/GetTelevisionShowByIdHandler.cs +++ b/ErsatzTV.Application/Television/Queries/GetTelevisionShowByIdHandler.cs @@ -1,5 +1,7 @@ -using System.Threading; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; +using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Repositories; using LanguageExt; using MediatR; @@ -9,15 +11,29 @@ namespace ErsatzTV.Application.Television.Queries { public class GetTelevisionShowByIdHandler : IRequestHandler> { + private readonly ISearchRepository _searchRepository; private readonly ITelevisionRepository _televisionRepository; - public GetTelevisionShowByIdHandler(ITelevisionRepository televisionRepository) => + public GetTelevisionShowByIdHandler( + ITelevisionRepository televisionRepository, + ISearchRepository searchRepository) + { _televisionRepository = televisionRepository; + _searchRepository = searchRepository; + } - public Task> Handle( + public async Task> Handle( GetTelevisionShowById request, - CancellationToken cancellationToken) => - _televisionRepository.GetShow(request.Id) - .MapT(ProjectToViewModel); + CancellationToken cancellationToken) + { + Option maybeShow = await _televisionRepository.GetShow(request.Id); + return await maybeShow.Match>>( + async show => + { + List languages = await _searchRepository.GetLanguagesForShow(show); + return ProjectToViewModel(show, languages); + }, + () => Task.FromResult(Option.None)); + } } } diff --git a/ErsatzTV.Application/Television/TelevisionShowViewModel.cs b/ErsatzTV.Application/Television/TelevisionShowViewModel.cs index e7a45fc0c..2ccfd46d0 100644 --- a/ErsatzTV.Application/Television/TelevisionShowViewModel.cs +++ b/ErsatzTV.Application/Television/TelevisionShowViewModel.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Globalization; namespace ErsatzTV.Application.Television { @@ -11,5 +12,6 @@ namespace ErsatzTV.Application.Television string FanArt, List Genres, List Tags, - List Studios); + List Studios, + List Languages); } diff --git a/ErsatzTV.Infrastructure/Data/Repositories/MovieRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/MovieRepository.cs index 13192279d..eaf14e0d4 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/MovieRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/MovieRepository.cs @@ -43,6 +43,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories .ThenInclude(m => m.Tags) .Include(m => m.MovieMetadata) .ThenInclude(m => m.Studios) + .Include(m => m.MediaVersions) + .ThenInclude(mv => mv.Streams) .OrderBy(m => m.Id) .SingleOrDefaultAsync(m => m.Id == movieId) .Map(Optional); diff --git a/ErsatzTV.Infrastructure/Search/SearchIndex.cs b/ErsatzTV.Infrastructure/Search/SearchIndex.cs index 37324326f..87dc33a00 100644 --- a/ErsatzTV.Infrastructure/Search/SearchIndex.cs +++ b/ErsatzTV.Infrastructure/Search/SearchIndex.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading.Tasks; using ErsatzTV.Core; @@ -47,15 +48,20 @@ namespace ErsatzTV.Infrastructure.Search private const string ShowType = "show"; private const string ArtistType = "artist"; private const string MusicVideoType = "music_video"; + private readonly List _cultureInfos; private readonly ILogger _logger; private FSDirectory _directory; private IndexWriter _writer; - public SearchIndex(ILogger logger) => _logger = logger; + public SearchIndex(ILogger logger) + { + _logger = logger; + _cultureInfos = CultureInfo.GetCultures(CultureTypes.NeutralCultures).ToList(); + } - public int Version => 6; + public int Version => 7; public Task Initialize(ILocalFileSystem localFileSystem) { @@ -296,11 +302,17 @@ namespace ErsatzTV.Infrastructure.Search if (maybeVersion.IsSome) { MediaVersion version = maybeVersion.ValueUnsafe(); - foreach (string lang in version.Streams.Filter(ms => ms.MediaStreamKind == MediaStreamKind.Video) + foreach (CultureInfo cultureInfo in version.Streams + .Filter(ms => ms.MediaStreamKind == MediaStreamKind.Audio) .Map(ms => ms.Language).Distinct() - .Filter(s => !string.IsNullOrWhiteSpace(s))) + .Filter(s => !string.IsNullOrWhiteSpace(s)) + .Map( + l => _cultureInfos.Filter( + c => string.Equals(c.ThreeLetterISOLanguageName, l, StringComparison.OrdinalIgnoreCase))) + .Sequence() + .Flatten()) { - doc.Add(new StringField(LanguageField, lang, Field.Store.NO)); + doc.Add(new TextField(LanguageField, cultureInfo.EnglishName, Field.Store.NO)); } } } @@ -326,9 +338,19 @@ namespace ErsatzTV.Infrastructure.Search }; List languages = await searchRepository.GetLanguagesForShow(show); - foreach (string lang in languages.Distinct().Filter(s => !string.IsNullOrWhiteSpace(s))) + foreach (CultureInfo cultureInfo in languages + .Distinct() + .Filter(s => !string.IsNullOrWhiteSpace(s)) + .Map( + l => _cultureInfos.Filter( + c => string.Equals( + c.ThreeLetterISOLanguageName, + l, + StringComparison.OrdinalIgnoreCase))) + .Sequence() + .Flatten()) { - doc.Add(new StringField(LanguageField, lang, Field.Store.NO)); + doc.Add(new TextField(LanguageField, cultureInfo.EnglishName, Field.Store.NO)); } if (metadata.ReleaseDate.HasValue) @@ -391,9 +413,19 @@ namespace ErsatzTV.Infrastructure.Search }; List languages = await searchRepository.GetLanguagesForArtist(artist); - foreach (string lang in languages.Distinct().Filter(s => !string.IsNullOrWhiteSpace(s))) + foreach (CultureInfo cultureInfo in languages + .Distinct() + .Filter(s => !string.IsNullOrWhiteSpace(s)) + .Map( + l => _cultureInfos.Filter( + c => string.Equals( + c.ThreeLetterISOLanguageName, + l, + StringComparison.OrdinalIgnoreCase))) + .Sequence() + .Flatten()) { - doc.Add(new StringField(LanguageField, lang, Field.Store.NO)); + doc.Add(new TextField(LanguageField, cultureInfo.EnglishName, Field.Store.NO)); } foreach (Genre genre in metadata.Genres) diff --git a/ErsatzTV/Pages/Artist.razor b/ErsatzTV/Pages/Artist.razor index f2b6f0416..dc0023c9a 100644 --- a/ErsatzTV/Pages/Artist.razor +++ b/ErsatzTV/Pages/Artist.razor @@ -5,6 +5,7 @@ @using ErsatzTV.Application.MediaCards.Queries @using ErsatzTV.Application.MediaCollections @using ErsatzTV.Application.MediaCollections.Commands +@using System.Globalization @using Unit = LanguageExt.Unit @inject IMediator Mediator @inject IDialogService Dialog @@ -57,6 +58,16 @@ + @if (_artist.Languages.Any()) + { + Languages +
+ @foreach (CultureInfo language in _artist.Languages.OrderBy(l => l.EnglishName)) + { + + } +
+ } @if (_artist.Genres.Any()) { Genres diff --git a/ErsatzTV/Pages/Movie.razor b/ErsatzTV/Pages/Movie.razor index f24323d95..7ab357b28 100644 --- a/ErsatzTV/Pages/Movie.razor +++ b/ErsatzTV/Pages/Movie.razor @@ -1,6 +1,7 @@ @page "/media/movies/{MovieId:int}" @using ErsatzTV.Application.Movies @using ErsatzTV.Application.Movies.Queries +@using System.Globalization @using ErsatzTV.Application.MediaCollections @using ErsatzTV.Application.MediaCollections.Commands @inject IMediator Mediator @@ -43,6 +44,16 @@ + @if (_movie.Languages.Any()) + { + Languages +
+ @foreach (CultureInfo language in _movie.Languages.OrderBy(l => l.EnglishName)) + { + + } +
+ } @if (_movie.Studios.Any()) { Studios diff --git a/ErsatzTV/Pages/TelevisionSeasonList.razor b/ErsatzTV/Pages/TelevisionSeasonList.razor index 650a91a77..d85ad4d0e 100644 --- a/ErsatzTV/Pages/TelevisionSeasonList.razor +++ b/ErsatzTV/Pages/TelevisionSeasonList.razor @@ -7,6 +7,7 @@ @using ErsatzTV.Application.MediaCollections.Commands @using ErsatzTV.Application.ProgramSchedules @using ErsatzTV.Application.ProgramSchedules.Commands +@using System.Globalization @using Unit = LanguageExt.Unit @inject IMediator Mediator @inject ILogger Logger @@ -58,6 +59,16 @@ + @if (_show.Languages.Any()) + { + Languages +
+ @foreach (CultureInfo language in _show.Languages.OrderBy(l => l.EnglishName)) + { + + } +
+ } @if (_show.Studios.Any()) { Studios