diff --git a/CHANGELOG.md b/CHANGELOG.md index 0317cdd50..70ea8ca13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Fix search results for `Other Videos` when NFO metadata is used - Properly synchronize tags from Emby movies and shows - Properly sync updated file paths from Plex +- Fix numeric range search queries (e.g. `minutes:[5 TO 10]`, `minutes:[* TO 3]`) ### Added - Add `QSV Device` option to ffmpeg profile on linux +- Add guids to search index (e.g. `imdb:tt000000`, `tvdb:12345`) ## [0.6.7-beta] - 2022-09-05 ### Fixed diff --git a/ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs index 67c434e4c..9cb6d7286 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs @@ -31,6 +31,8 @@ public class SearchRepository : ISearchRepository .ThenInclude(mm => mm.Directors) .Include(mi => (mi as Movie).MovieMetadata) .ThenInclude(mm => mm.Writers) + .Include(mi => (mi as Movie).MovieMetadata) + .ThenInclude(em => em.Guids) .Include(mi => (mi as Movie).MediaVersions) .ThenInclude(mm => mm.Streams) .Include(mi => (mi as Episode).EpisodeMetadata) @@ -81,6 +83,8 @@ public class SearchRepository : ISearchRepository .ThenInclude(mm => mm.Studios) .Include(mi => (mi as Show).ShowMetadata) .ThenInclude(mm => mm.Actors) + .Include(mi => (mi as Show).ShowMetadata) + .ThenInclude(mm => mm.Guids) .Include(mi => (mi as MusicVideo).Artist) .ThenInclude(mm => mm.ArtistMetadata) .Include(mi => (mi as MusicVideo).MusicVideoMetadata) @@ -91,6 +95,8 @@ public class SearchRepository : ISearchRepository .ThenInclude(mm => mm.Tags) .Include(mi => (mi as MusicVideo).MusicVideoMetadata) .ThenInclude(mm => mm.Studios) + .Include(mi => (mi as MusicVideo).MusicVideoMetadata) + .ThenInclude(mm => mm.Guids) .Include(mi => (mi as MusicVideo).MediaVersions) .ThenInclude(mm => mm.Streams) .Include(mi => (mi as Artist).ArtistMetadata) @@ -99,6 +105,8 @@ public class SearchRepository : ISearchRepository .ThenInclude(mm => mm.Styles) .Include(mi => (mi as Artist).ArtistMetadata) .ThenInclude(mm => mm.Moods) + .Include(mi => (mi as Artist).ArtistMetadata) + .ThenInclude(mm => mm.Guids) .Include(mi => (mi as OtherVideo).OtherVideoMetadata) .ThenInclude(ovm => ovm.Genres) .Include(mi => (mi as OtherVideo).OtherVideoMetadata) @@ -111,12 +119,16 @@ public class SearchRepository : ISearchRepository .ThenInclude(ovm => ovm.Directors) .Include(mi => (mi as OtherVideo).OtherVideoMetadata) .ThenInclude(ovm => ovm.Writers) + .Include(mi => (mi as OtherVideo).OtherVideoMetadata) + .ThenInclude(ovm => ovm.Guids) .Include(mi => (mi as OtherVideo).MediaVersions) .ThenInclude(ovm => ovm.Streams) .Include(mi => (mi as Song).SongMetadata) .ThenInclude(mm => mm.Tags) .Include(mi => (mi as Song).SongMetadata) .ThenInclude(mm => mm.Genres) + .Include(mi => (mi as Song).SongMetadata) + .ThenInclude(mm => mm.Guids) .Include(mi => (mi as Song).MediaVersions) .ThenInclude(mm => mm.Streams) .Include(mi => mi.TraktListItems) @@ -187,6 +199,8 @@ public class SearchRepository : ISearchRepository .ThenInclude(mm => mm.Directors) .Include(mi => (mi as Movie).MovieMetadata) .ThenInclude(mm => mm.Writers) + .Include(mi => (mi as Movie).MovieMetadata) + .ThenInclude(mm => mm.Guids) .Include(mi => (mi as Movie).MediaVersions) .ThenInclude(mm => mm.Streams) .Include(mi => (mi as Episode).EpisodeMetadata) @@ -223,6 +237,8 @@ public class SearchRepository : ISearchRepository .ThenInclude(sm => sm.Studios) .Include(mi => (mi as Season).SeasonMetadata) .ThenInclude(sm => sm.Actors) + .Include(mi => (mi as Season).SeasonMetadata) + .ThenInclude(sm => sm.Guids) .Include(mi => (mi as Season).Show) .ThenInclude(sm => sm.ShowMetadata) .ThenInclude(sm => sm.Genres) @@ -237,6 +253,8 @@ public class SearchRepository : ISearchRepository .ThenInclude(mm => mm.Studios) .Include(mi => (mi as Show).ShowMetadata) .ThenInclude(mm => mm.Actors) + .Include(mi => (mi as Show).ShowMetadata) + .ThenInclude(mm => mm.Guids) .Include(mi => (mi as MusicVideo).Artist) .ThenInclude(mm => mm.ArtistMetadata) .Include(mi => (mi as MusicVideo).MusicVideoMetadata) @@ -247,6 +265,8 @@ public class SearchRepository : ISearchRepository .ThenInclude(mm => mm.Tags) .Include(mi => (mi as MusicVideo).MusicVideoMetadata) .ThenInclude(mm => mm.Studios) + .Include(mi => (mi as MusicVideo).MusicVideoMetadata) + .ThenInclude(mm => mm.Guids) .Include(mi => (mi as MusicVideo).MediaVersions) .ThenInclude(mm => mm.Streams) .Include(mi => (mi as Artist).ArtistMetadata) @@ -255,6 +275,8 @@ public class SearchRepository : ISearchRepository .ThenInclude(mm => mm.Styles) .Include(mi => (mi as Artist).ArtistMetadata) .ThenInclude(mm => mm.Moods) + .Include(mi => (mi as Artist).ArtistMetadata) + .ThenInclude(mm => mm.Guids) .Include(mi => (mi as OtherVideo).OtherVideoMetadata) .ThenInclude(ovm => ovm.Genres) .Include(mi => (mi as OtherVideo).OtherVideoMetadata) @@ -267,12 +289,16 @@ public class SearchRepository : ISearchRepository .ThenInclude(ovm => ovm.Directors) .Include(mi => (mi as OtherVideo).OtherVideoMetadata) .ThenInclude(ovm => ovm.Writers) + .Include(mi => (mi as OtherVideo).OtherVideoMetadata) + .ThenInclude(ovm => ovm.Guids) .Include(mi => (mi as OtherVideo).MediaVersions) .ThenInclude(ovm => ovm.Streams) .Include(mi => (mi as Song).SongMetadata) .ThenInclude(mm => mm.Tags) .Include(mi => (mi as Song).SongMetadata) .ThenInclude(mm => mm.Genres) + .Include(mi => (mi as Song).SongMetadata) + .ThenInclude(mm => mm.Guids) .Include(mi => (mi as Song).MediaVersions) .ThenInclude(mm => mm.Streams) .Include(mi => mi.TraktListItems) diff --git a/ErsatzTV.Infrastructure/Search/SearchIndex.cs b/ErsatzTV.Infrastructure/Search/SearchIndex.cs index daabf553d..3fe6ef9f9 100644 --- a/ErsatzTV.Infrastructure/Search/SearchIndex.cs +++ b/ErsatzTV.Infrastructure/Search/SearchIndex.cs @@ -89,7 +89,7 @@ public sealed class SearchIndex : ISearchIndex _initialized = false; } - public int Version => 31; + public int Version => 32; public async Task Initialize( ILocalFileSystem localFileSystem, @@ -251,7 +251,7 @@ public sealed class SearchIndex : ISearchIndex return Unit.Default; } - private bool ValidateDirectory(string folder) + private static bool ValidateDirectory(string folder) { try { @@ -450,6 +450,8 @@ public sealed class SearchIndex : ISearchIndex { doc.Add(new StringField(TraktListField, item.TraktList.TraktId.ToString(), Field.Store.NO)); } + + AddMetadataGuids(metadata, doc); _writer.UpdateDocument(new Term(IdField, movie.Id.ToString()), doc); } @@ -570,6 +572,8 @@ public sealed class SearchIndex : ISearchIndex { doc.Add(new StringField(TraktListField, item.TraktList.TraktId.ToString(), Field.Store.NO)); } + + AddMetadataGuids(metadata, doc); _writer.UpdateDocument(new Term(IdField, show.Id.ToString()), doc); } @@ -656,6 +660,8 @@ public sealed class SearchIndex : ISearchIndex { doc.Add(new TextField(TagField, tag.Name, Field.Store.NO)); } + + AddMetadataGuids(metadata, doc); _writer.UpdateDocument(new Term(IdField, season.Id.ToString()), doc); } @@ -708,6 +714,8 @@ public sealed class SearchIndex : ISearchIndex { doc.Add(new TextField(MoodField, mood.Name, Field.Store.NO)); } + + AddMetadataGuids(metadata, doc); _writer.UpdateDocument(new Term(IdField, artist.Id.ToString()), doc); } @@ -808,6 +816,8 @@ public sealed class SearchIndex : ISearchIndex { doc.Add(new TextField(ArtistField, artist, Field.Store.NO)); } + + AddMetadataGuids(metadata, doc); _writer.UpdateDocument(new Term(IdField, musicVideo.Id.ToString()), doc); } @@ -941,6 +951,8 @@ public sealed class SearchIndex : ISearchIndex doc.Add(new StringField(TraktListField, item.TraktList.TraktId.ToString(), Field.Store.NO)); } + AddMetadataGuids(metadata, doc); + _writer.UpdateDocument(new Term(IdField, episode.Id.ToString()), doc); } catch (Exception ex) @@ -1039,6 +1051,8 @@ public sealed class SearchIndex : ISearchIndex { doc.Add(new TextField(WriterField, writer.Name, Field.Store.NO)); } + + AddMetadataGuids(metadata, doc); _writer.UpdateDocument(new Term(IdField, otherVideo.Id.ToString()), doc); } @@ -1107,6 +1121,8 @@ public sealed class SearchIndex : ISearchIndex { doc.Add(new TextField(GenreField, genre.Name, Field.Store.NO)); } + + AddMetadataGuids(metadata, doc); _writer.UpdateDocument(new Term(IdField, song.Id.ToString()), doc); } @@ -1118,11 +1134,11 @@ public sealed class SearchIndex : ISearchIndex } } - private SearchItem ProjectToSearchItem(Document doc) => new( + private static SearchItem ProjectToSearchItem(Document doc) => new( doc.Get(TypeField), Convert.ToInt32(doc.Get(IdField))); - private Query ParseQuery(string searchQuery, QueryParser parser) + private static Query ParseQuery(string searchQuery, QueryParser parser) { Query query; try @@ -1137,23 +1153,38 @@ public sealed class SearchIndex : ISearchIndex return query; } + private static void AddMetadataGuids(Metadata metadata, Document doc) + { + foreach (MetadataGuid guid in metadata.Guids) + { + string[] split = (guid.Guid ?? string.Empty).Split("://"); + if (split.Length == 2 && !string.IsNullOrWhiteSpace(split[1])) + { + doc.Add(new StringField(split[0], split[1].ToLowerInvariant(), Field.Store.NO)); + } + } + } + // this is used for filtering duplicate search results private static string GetTitleAndYear(Metadata metadata) => metadata switch { EpisodeMetadata em => - $"{em.Title}_{em.Episode.Season.Show.ShowMetadata.Head().Title}_{em.Year}_{em.Episode.Season.SeasonNumber}_{em.EpisodeNumber}_{em.Episode.State}" + $"{Title(em)}_{em.Episode.Season.Show.ShowMetadata.Head().Title}_{em.Year}_{em.Episode.Season.SeasonNumber}_{em.EpisodeNumber}_{em.Episode.State}" .ToLowerInvariant(), - OtherVideoMetadata ovm => $"{OtherVideoTitle(ovm)}_{ovm.Year}_{ovm.OtherVideo.State}".ToLowerInvariant(), - SongMetadata sm => $"{sm.Title}_{sm.Year}_{sm.Song.State}".ToLowerInvariant(), - MovieMetadata mm => $"{mm.Title}_{mm.Year}_{mm.Movie.State}".ToLowerInvariant(), - ArtistMetadata am => $"{am.Title}_{am.Year}_{am.Artist.State}".ToLowerInvariant(), - MusicVideoMetadata mvm => $"{mvm.Title}_{mvm.Year}_{mvm.MusicVideo.State}".ToLowerInvariant(), - SeasonMetadata sm => $"{sm.Title}_{sm.Year}_{sm.Season.State}".ToLowerInvariant(), - ShowMetadata sm => $"{sm.Title}_{sm.Year}_{sm.Show.State}".ToLowerInvariant(), - _ => $"{metadata.Title}_{metadata.Year}".ToLowerInvariant() + OtherVideoMetadata ovm => $"{OtherVideoTitle(ovm).Replace(' ', '_')}_{ovm.Year}_{ovm.OtherVideo.State}".ToLowerInvariant(), + SongMetadata sm => $"{Title(sm)}_{sm.Year}_{sm.Song.State}".ToLowerInvariant(), + MovieMetadata mm => $"{Title(mm)}_{mm.Year}_{mm.Movie.State}".ToLowerInvariant(), + ArtistMetadata am => $"{Title(am)}_{am.Year}_{am.Artist.State}".ToLowerInvariant(), + MusicVideoMetadata mvm => $"{Title(mvm)}_{mvm.Year}_{mvm.MusicVideo.State}".ToLowerInvariant(), + SeasonMetadata sm => $"{Title(sm)}_{sm.Year}_{sm.Season.State}".ToLowerInvariant(), + ShowMetadata sm => $"{Title(sm)}_{sm.Year}_{sm.Show.State}".ToLowerInvariant(), + _ => $"{Title(metadata)}_{metadata.Year}".ToLowerInvariant() }; + private static string Title(Metadata metadata) => + metadata.Title.Replace(' ', '_'); + private static string GetJumpLetter(Metadata metadata) { char c = (metadata.SortTitle ?? " ").ToLowerInvariant().Head();