From b21d16b0f1d677aaa8260be44e0c7a895eade8ee Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Wed, 20 Mar 2024 19:46:19 -0500 Subject: [PATCH] add show_content_rating to search index (#1647) --- CHANGELOG.md | 2 +- .../Search/ElasticSearchIndex.cs | 7 +++- .../Search/LuceneSearchIndex.cs | 39 +++++++++++++++---- .../Search/Models/ElasticSearchItem.cs | 3 ++ 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aca1acfc..27a120dd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Added -- Add `show_studio` to search index for seasons and episodes +- Add `show_studio` and `show_content_rating` to search index for seasons and episodes - Add two new global subtitle settings: - `Use embedded subtitles` - Default value: `true` diff --git a/ErsatzTV.Infrastructure/Search/ElasticSearchIndex.cs b/ErsatzTV.Infrastructure/Search/ElasticSearchIndex.cs index 0431bd913..02125a65e 100644 --- a/ErsatzTV.Infrastructure/Search/ElasticSearchIndex.cs +++ b/ErsatzTV.Infrastructure/Search/ElasticSearchIndex.cs @@ -46,7 +46,7 @@ public class ElasticSearchIndex : ISearchIndex return exists.IsValidResponse; } - public int Version => 41; + public int Version => 42; public async Task Initialize( ILocalFileSystem localFileSystem, @@ -162,7 +162,7 @@ public class ElasticSearchIndex : ISearchIndex var items = new List(); var totalCount = 0; - Query parsedQuery = LuceneSearchIndex.ParseQuery(query); + Query parsedQuery = LuceneSearchIndex.ParseQuery(query, false); SearchResponse response = await _client.SearchAsync( s => s.Index(IndexName) @@ -238,6 +238,7 @@ public class ElasticSearchIndex : ISearchIndex .Text(t => t.ShowGenre, t => t.Store(false)) .Text(t => t.ShowTag, t => t.Store(false)) .Text(t => t.ShowStudio, t => t.Store(false)) + .Keyword(t => t.ShowContentRating, t => t.Store(false)) .Text(t => t.Style, t => t.Store(false)) .Text(t => t.Mood, t => t.Store(false)) .Text(t => t.Album, t => t.Store(false)) @@ -413,6 +414,7 @@ public class ElasticSearchIndex : ISearchIndex ShowGenre = showMetadata.Genres.Map(g => g.Name).ToList(), ShowTag = showMetadata.Tags.Map(t => t.Name).ToList(), ShowStudio = showMetadata.Studios.Map(s => s.Name).ToList(), + ShowContentRating = GetContentRatings(showMetadata.ContentRating), Language = await GetLanguages( searchRepository, await searchRepository.GetLanguagesForSeason(season)), @@ -614,6 +616,7 @@ public class ElasticSearchIndex : ISearchIndex doc.ShowGenre = showMetadata.Genres.Map(g => g.Name).ToList(); doc.ShowTag = showMetadata.Tags.Map(t => t.Name).ToList(); doc.ShowStudio = showMetadata.Studios.Map(s => s.Name).ToList(); + doc.ShowContentRating = GetContentRatings(showMetadata.ContentRating); } AddStatistics(doc, episode.MediaVersions); diff --git a/ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs b/ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs index 290565ae4..6e2984adf 100644 --- a/ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs +++ b/ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs @@ -64,6 +64,7 @@ public sealed class LuceneSearchIndex : ISearchIndex internal const string ShowGenreField = "show_genre"; internal const string ShowTagField = "show_tag"; internal const string ShowStudioField = "show_studio"; + internal const string ShowContentRatingField = "show_content_rating"; internal const string MetadataKindField = "metadata_kind"; internal const string VideoCodecField = "video_codec"; internal const string VideoDynamicRange = "video_dynamic_range"; @@ -113,7 +114,7 @@ public sealed class LuceneSearchIndex : ISearchIndex return Task.FromResult(directoryExists && fileExists); } - public int Version => 41; + public int Version => 42; public async Task Initialize( ILocalFileSystem localFileSystem, @@ -735,6 +736,15 @@ public sealed class LuceneSearchIndex : ISearchIndex { doc.Add(new TextField(ShowStudioField, studio.Name, Field.Store.NO)); } + + if (!string.IsNullOrWhiteSpace(showMetadata.ContentRating)) + { + foreach (string contentRating in (showMetadata.ContentRating ?? string.Empty).Split("/") + .Map(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x))) + { + doc.Add(new StringField(ShowContentRatingField, contentRating, Field.Store.NO)); + } + } List languages = await searchRepository.GetLanguagesForSeason(season); await AddLanguages(searchRepository, doc, languages); @@ -1017,8 +1027,17 @@ public sealed class LuceneSearchIndex : ISearchIndex { doc.Add(new TextField(ShowStudioField, studio.Name, Field.Store.NO)); } + + if (!string.IsNullOrWhiteSpace(showMetadata.ContentRating)) + { + foreach (string contentRating in (showMetadata.ContentRating ?? string.Empty).Split("/") + .Map(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x))) + { + doc.Add(new StringField(ShowContentRatingField, contentRating, Field.Store.NO)); + } + } } - + if (!string.IsNullOrWhiteSpace(metadata.Title)) { doc.Add(new TextField(TitleField, metadata.Title, Field.Store.NO)); @@ -1353,16 +1372,20 @@ public sealed class LuceneSearchIndex : ISearchIndex doc.Get(TypeField, CultureInfo.InvariantCulture), Convert.ToInt32(doc.Get(IdField, CultureInfo.InvariantCulture), CultureInfo.InvariantCulture)); - internal static Query ParseQuery(string query) + internal static Query ParseQuery(string query, bool useCustomAnalyzers = true) { using var analyzer = new StandardAnalyzer(AppLuceneVersion); - var customAnalyzers = new Dictionary + var customAnalyzers = new Dictionary(); + + if (useCustomAnalyzers) { - { ContentRatingField, new KeywordAnalyzer() }, - { StateField, new KeywordAnalyzer() } - }; + customAnalyzers.Add(ShowContentRatingField, new KeywordAnalyzer()); + customAnalyzers.Add(ContentRatingField, new KeywordAnalyzer()); + customAnalyzers.Add(StateField, new KeywordAnalyzer()); + } + using var analyzerWrapper = new PerFieldAnalyzerWrapper(analyzer, customAnalyzers); - QueryParser parser = new CustomMultiFieldQueryParser(AppLuceneVersion, new[] { TitleField }, analyzerWrapper); + QueryParser parser = new CustomMultiFieldQueryParser(AppLuceneVersion, [TitleField], analyzerWrapper); parser.AllowLeadingWildcard = true; return ParseQuery(query, parser); } diff --git a/ErsatzTV.Infrastructure/Search/Models/ElasticSearchItem.cs b/ErsatzTV.Infrastructure/Search/Models/ElasticSearchItem.cs index 068446b7b..7e9fc18c8 100644 --- a/ErsatzTV.Infrastructure/Search/Models/ElasticSearchItem.cs +++ b/ErsatzTV.Infrastructure/Search/Models/ElasticSearchItem.cs @@ -117,6 +117,9 @@ public class ElasticSearchItem : MinimalElasticSearchItem [JsonPropertyName(LuceneSearchIndex.ShowStudioField)] public List ShowStudio { get; set; } + + [JsonPropertyName(LuceneSearchIndex.ShowContentRatingField)] + public List ShowContentRating { get; set; } [JsonPropertyName(LuceneSearchIndex.StyleField)] public List Style { get; set; }