Browse Source

add more search fields (#766)

* properly index show and season state

* add height, width, season_number, episode_number to search index

* update docs
pull/769/head
Jason Dove 4 years ago committed by GitHub
parent
commit
837f311ec0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CHANGELOG.md
  2. 18
      ErsatzTV.Infrastructure/Health/Checks/FileNotFoundHealthCheck.cs
  3. 25
      ErsatzTV.Infrastructure/Health/Checks/UnavailableHealthCheck.cs
  4. 4
      ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs
  5. 9
      ErsatzTV.Infrastructure/Search/CustomMultiFieldQueryParser.cs
  6. 15
      ErsatzTV.Infrastructure/Search/CustomQueryParser.cs
  7. 30
      ErsatzTV.Infrastructure/Search/SearchIndex.cs
  8. 16
      docs/user-guide/search.md

2
CHANGELOG.md

@ -16,6 +16,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -16,6 +16,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added
- Add `unavailable` state for Emby movie libraries
- Add `height` and `width` to search index for all videos
- Add `season_number` and `episode_number` to search index for all episodes
## [0.5.3-beta] - 2022-04-24
### Fixed

18
ErsatzTV.Infrastructure/Health/Checks/FileNotFoundHealthCheck.cs

@ -31,20 +31,26 @@ public class FileNotFoundHealthCheck : BaseHealthCheck, IFileNotFoundHealthCheck @@ -31,20 +31,26 @@ public class FileNotFoundHealthCheck : BaseHealthCheck, IFileNotFoundHealthCheck
.Include(mi => (mi as OtherVideo).MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.Include(mi => (mi as Song).MediaVersions)
.ThenInclude(mv => mv.MediaFiles);
.ThenInclude(mv => mv.MediaFiles)
.Include(mi => (mi as Show).ShowMetadata)
.Include(mi => (mi as Season).Show)
.ThenInclude(s => s.ShowMetadata)
.Include(mi => (mi as Season).SeasonMetadata);
List<MediaItem> five = await mediaItems
// shows and seasons don't have paths to display
.Filter(mi => !(mi is Show))
.Filter(mi => !(mi is Season))
.OrderBy(mi => mi.Id)
.Take(5)
.ToListAsync(cancellationToken);
if (mediaItems.Any())
{
IEnumerable<string> paths = five.Map(mi => mi.GetHeadVersion().MediaFiles.Head().Path);
IEnumerable<string> paths = five.Map(
mi => mi switch
{
Show s => s.ShowMetadata.Head().Title,
Season s => $"{s.Show.ShowMetadata.Head().Title} Season {s.SeasonNumber}",
_ => mi.GetHeadVersion().MediaFiles.Head().Path
});
var files = string.Join(", ", paths);

25
ErsatzTV.Infrastructure/Health/Checks/UnavailableHealthCheck.cs

@ -46,13 +46,13 @@ public class UnavailableHealthCheck : BaseHealthCheck, IUnavailableHealthCheck @@ -46,13 +46,13 @@ public class UnavailableHealthCheck : BaseHealthCheck, IUnavailableHealthCheck
.Include(mi => (mi as OtherVideo).MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.Include(mi => (mi as Song).MediaVersions)
.ThenInclude(mv => mv.MediaFiles);
.ThenInclude(mv => mv.MediaFiles)
.Include(mi => (mi as Show).ShowMetadata)
.Include(mi => (mi as Season).Show)
.ThenInclude(s => s.ShowMetadata)
.Include(mi => (mi as Season).SeasonMetadata);
List<MediaItem> five = await mediaItems
// shows and seasons don't have paths to display
.Filter(mi => !(mi is Show))
.Filter(mi => !(mi is Season))
.OrderBy(mi => mi.Id)
.Take(5)
.ToListAsync(cancellationToken);
@ -63,11 +63,16 @@ public class UnavailableHealthCheck : BaseHealthCheck, IUnavailableHealthCheck @@ -63,11 +63,16 @@ public class UnavailableHealthCheck : BaseHealthCheck, IUnavailableHealthCheck
foreach (MediaItem mediaItem in five)
{
string path = await mediaItem.GetLocalPath(
_plexPathReplacementService,
_jellyfinPathReplacementService,
_embyPathReplacementService,
false);
string path = mediaItem switch
{
Show s => s.ShowMetadata.Head().Title,
Season s => $"{s.Show.ShowMetadata.Head().Title} Season {s.SeasonNumber}",
_ => await mediaItem.GetLocalPath(
_plexPathReplacementService,
_jellyfinPathReplacementService,
_embyPathReplacementService,
false)
};
paths.Add(path);
}

4
ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs

@ -100,7 +100,9 @@ public class PlexServerApiClient : IPlexServerApiClient @@ -100,7 +100,9 @@ public class PlexServerApiClient : IPlexServerApiClient
IPlexServerApi service = RestService.For<IPlexServerApi>(connection.Uri);
return await service.GetLibrarySectionContents(library.Key, token.AuthToken)
.Map(r => r.MediaContainer.Metadata)
.Map(list => list.Map(metadata => ProjectToShow(metadata, library.MediaSourceId)).ToList());
.Map(
list => (list ?? new List<PlexMetadataResponse>())
.Map(metadata => ProjectToShow(metadata, library.MediaSourceId)).ToList());
}
catch (Exception ex)
{

9
ErsatzTV.Infrastructure/Search/CustomMultiFieldQueryParser.cs

@ -32,11 +32,11 @@ public class CustomMultiFieldQueryParser : MultiFieldQueryParser @@ -32,11 +32,11 @@ public class CustomMultiFieldQueryParser : MultiFieldQueryParser
return base.GetWildcardQuery("release_date", todayString);
}
if (field == "minutes" && int.TryParse(queryText, out int val))
if (CustomQueryParser.NumericFields.Contains(field) && int.TryParse(queryText, out int val))
{
var bytesRef = new BytesRef();
NumericUtils.Int32ToPrefixCoded(val, 0, bytesRef);
return NewTermQuery(new Term("minutes", bytesRef));
return NewTermQuery(new Term(field, bytesRef));
}
return base.GetFieldQuery(field, queryText, quoted);
@ -84,9 +84,10 @@ public class CustomMultiFieldQueryParser : MultiFieldQueryParser @@ -84,9 +84,10 @@ public class CustomMultiFieldQueryParser : MultiFieldQueryParser
bool startInclusive,
bool endInclusive)
{
if (field == "minutes" && int.TryParse(part1, out int min) && int.TryParse(part2, out int max))
if (CustomQueryParser.NumericFields.Contains(field) && int.TryParse(part1, out int min) &&
int.TryParse(part2, out int max))
{
return NumericRangeQuery.NewInt32Range(field, min, max, startInclusive, endInclusive);
return NumericRangeQuery.NewInt32Range(field, 1, min, max, startInclusive, endInclusive);
}
return base.GetRangeQuery(field, part1, part2, startInclusive, endInclusive);

15
ErsatzTV.Infrastructure/Search/CustomQueryParser.cs

@ -10,6 +10,15 @@ namespace ErsatzTV.Infrastructure.Search; @@ -10,6 +10,15 @@ namespace ErsatzTV.Infrastructure.Search;
public class CustomQueryParser : QueryParser
{
internal static readonly List<string> NumericFields = new()
{
SearchIndex.MinutesField,
SearchIndex.HeightField,
SearchIndex.WidthField,
SearchIndex.SeasonNumberField,
SearchIndex.EpisodeNumberField
};
public CustomQueryParser(LuceneVersion matchVersion, string f, Analyzer a) : base(matchVersion, f, a)
{
}
@ -30,11 +39,11 @@ public class CustomQueryParser : QueryParser @@ -30,11 +39,11 @@ public class CustomQueryParser : QueryParser
return base.GetWildcardQuery("release_date", todayString);
}
if (field == "minutes" && int.TryParse(queryText, out int val))
if (NumericFields.Contains(field) && int.TryParse(queryText, out int val))
{
var bytesRef = new BytesRef();
NumericUtils.Int32ToPrefixCoded(val, 0, bytesRef);
return NewTermQuery(new Term("minutes", bytesRef));
return NewTermQuery(new Term(field, bytesRef));
}
return base.GetFieldQuery(field, queryText, quoted);
@ -82,7 +91,7 @@ public class CustomQueryParser : QueryParser @@ -82,7 +91,7 @@ public class CustomQueryParser : QueryParser
bool startInclusive,
bool endInclusive)
{
if (field == "minutes" && int.TryParse(part1, out int min) && int.TryParse(part2, out int max))
if (NumericFields.Contains(field) && int.TryParse(part1, out int min) && int.TryParse(part2, out int max))
{
return NumericRangeQuery.NewInt32Range(field, 1, min, max, startInclusive, endInclusive);
}

30
ErsatzTV.Infrastructure/Search/SearchIndex.cs

@ -49,10 +49,14 @@ public sealed class SearchIndex : ISearchIndex @@ -49,10 +49,14 @@ public sealed class SearchIndex : ISearchIndex
private const string WriterField = "writer";
private const string TraktListField = "trakt_list";
private const string AlbumField = "album";
private const string MinutesField = "minutes";
internal const string MinutesField = "minutes";
private const string ArtistField = "artist";
private const string StateField = "state";
private const string AlbumArtistField = "album_artist";
internal const string HeightField = "height";
internal const string WidthField = "width";
internal const string SeasonNumberField = "season_number";
internal const string EpisodeNumberField = "episode_number";
public const string MovieType = "movie";
public const string ShowType = "show";
@ -77,7 +81,7 @@ public sealed class SearchIndex : ISearchIndex @@ -77,7 +81,7 @@ public sealed class SearchIndex : ISearchIndex
_initialized = false;
}
public int Version => 20;
public int Version => 21;
public Task<bool> Initialize(ILocalFileSystem localFileSystem)
{
@ -329,6 +333,9 @@ public sealed class SearchIndex : ISearchIndex @@ -329,6 +333,9 @@ public sealed class SearchIndex : ISearchIndex
{
doc.Add(
new Int32Field(MinutesField, (int)Math.Ceiling(version.Duration.TotalMinutes), Field.Store.NO));
doc.Add(new Int32Field(HeightField, version.Height, Field.Store.NO));
doc.Add(new Int32Field(WidthField, version.Width, Field.Store.NO));
}
if (!string.IsNullOrWhiteSpace(metadata.ContentRating))
@ -453,7 +460,8 @@ public sealed class SearchIndex : ISearchIndex @@ -453,7 +460,8 @@ public sealed class SearchIndex : ISearchIndex
new TextField(LibraryNameField, show.LibraryPath.Library.Name, Field.Store.NO),
new StringField(LibraryIdField, show.LibraryPath.Library.Id.ToString(), Field.Store.NO),
new StringField(TitleAndYearField, GetTitleAndYear(metadata), Field.Store.NO),
new StringField(JumpLetterField, GetJumpLetter(metadata), Field.Store.YES)
new StringField(JumpLetterField, GetJumpLetter(metadata), Field.Store.YES),
new StringField(StateField, show.State.ToString(), Field.Store.NO)
};
List<string> languages = await searchRepository.GetLanguagesForShow(show);
@ -545,7 +553,8 @@ public sealed class SearchIndex : ISearchIndex @@ -545,7 +553,8 @@ public sealed class SearchIndex : ISearchIndex
new TextField(LibraryNameField, season.LibraryPath.Library.Name, Field.Store.NO),
new StringField(LibraryIdField, season.LibraryPath.Library.Id.ToString(), Field.Store.NO),
new StringField(TitleAndYearField, titleAndYear, Field.Store.NO),
new StringField(JumpLetterField, GetJumpLetter(showMetadata), Field.Store.YES)
new StringField(JumpLetterField, GetJumpLetter(showMetadata), Field.Store.YES),
new StringField(StateField, season.State.ToString(), Field.Store.NO)
};
List<string> languages = await searchRepository.GetLanguagesForSeason(season);
@ -670,6 +679,9 @@ public sealed class SearchIndex : ISearchIndex @@ -670,6 +679,9 @@ public sealed class SearchIndex : ISearchIndex
{
doc.Add(
new Int32Field(MinutesField, (int)Math.Ceiling(version.Duration.TotalMinutes), Field.Store.NO));
doc.Add(new Int32Field(HeightField, version.Height, Field.Store.NO));
doc.Add(new Int32Field(WidthField, version.Width, Field.Store.NO));
}
if (metadata.ReleaseDate.HasValue)
@ -753,7 +765,9 @@ public sealed class SearchIndex : ISearchIndex @@ -753,7 +765,9 @@ public sealed class SearchIndex : ISearchIndex
new StringField(LibraryIdField, episode.LibraryPath.Library.Id.ToString(), Field.Store.NO),
new StringField(TitleAndYearField, GetTitleAndYear(metadata), Field.Store.NO),
new StringField(JumpLetterField, GetJumpLetter(metadata), Field.Store.YES),
new StringField(StateField, episode.State.ToString(), Field.Store.NO)
new StringField(StateField, episode.State.ToString(), Field.Store.NO),
new Int32Field(SeasonNumberField, episode.Season?.SeasonNumber ?? 0, Field.Store.NO),
new Int32Field(EpisodeNumberField, metadata.EpisodeNumber, Field.Store.NO)
};
await AddLanguages(searchRepository, doc, episode.MediaVersions);
@ -762,6 +776,9 @@ public sealed class SearchIndex : ISearchIndex @@ -762,6 +776,9 @@ public sealed class SearchIndex : ISearchIndex
{
doc.Add(
new Int32Field(MinutesField, (int)Math.Ceiling(version.Duration.TotalMinutes), Field.Store.NO));
doc.Add(new Int32Field(HeightField, version.Height, Field.Store.NO));
doc.Add(new Int32Field(WidthField, version.Width, Field.Store.NO));
}
if (metadata.ReleaseDate.HasValue)
@ -853,6 +870,9 @@ public sealed class SearchIndex : ISearchIndex @@ -853,6 +870,9 @@ public sealed class SearchIndex : ISearchIndex
{
doc.Add(
new Int32Field(MinutesField, (int)Math.Ceiling(version.Duration.TotalMinutes), Field.Store.NO));
doc.Add(new Int32Field(HeightField, version.Height, Field.Store.NO));
doc.Add(new Int32Field(WidthField, version.Width, Field.Store.NO));
}
doc.Add(new StringField(AddedDateField, metadata.DateAdded.ToString("yyyyMMdd"), Field.Store.NO));

16
docs/user-guide/search.md

@ -25,7 +25,9 @@ The following fields are available for searching movies: @@ -25,7 +25,9 @@ The following fields are available for searching movies:
- `language`: The movie audio stream language
- `release_date`: The movie release date (YYYYMMDD)
- `added_date`: The date the movie was added to ErsatzTV (YYYYMMDD)
- `minutes`: the rounded-up whole number duration of the movie in minutes
- `minutes`: The rounded-up whole number duration of the movie in minutes
- `height`: The movie height
- `width`: The movie width
- `type`: Always `movie`
### Shows
@ -57,7 +59,11 @@ The following fields are available for searching episodes: @@ -57,7 +59,11 @@ The following fields are available for searching episodes:
- `language`: The episode audio stream language
- `release_date`: The episode release date (YYYYMMDD)
- `added_date`: The date the episode was added to ErsatzTV (YYYYMMDD)
- `minutes`: the rounded-up whole number duration of the episode in minutes
- `minutes`: The rounded-up whole number duration of the episode in minutes
- `height`: The episode height
- `width`: The episode width
- `season_number`: The episode season number
- `episode_number`: The episode number
- `type`: Always `episode`
### Artists
@ -84,7 +90,9 @@ The following fields are available for searching music videos: @@ -84,7 +90,9 @@ The following fields are available for searching music videos:
- `language`: The music video audio stream language
- `release_date`: The music video release date (YYYYMMDD)
- `added_date`: The date the music video was added to ErsatzTV (YYYYMMDD)
- `minutes`: the rounded-up whole number duration of the music video in minutes
- `minutes`: The rounded-up whole number duration of the music video in minutes
- `height`: The music video height
- `width`: The music video width
- `type`: Always `music_video`
### Other Videos
@ -95,6 +103,8 @@ The following fields are available for searching other videos: @@ -95,6 +103,8 @@ The following fields are available for searching other videos:
- `tag`: All of the video's parent folders
- `minutes`: the rounded-up whole number duration of the video in minutes
- `added_date`: The date the other video was added to ErsatzTV (YYYYMMDD)
- `height`: The other video height
- `width`: The other video width
- `type`: Always `other_video`
### Songs

Loading…
Cancel
Save