Browse Source

add movie nfo country to search index (#2173)

pull/2176/head
Jason Dove 4 weeks ago committed by GitHub
parent
commit
c29788bc3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 1
      ErsatzTV.Core/Domain/Metadata/Tag.cs
  3. 8
      ErsatzTV.Infrastructure/Search/ElasticSearchIndex.cs
  4. 15
      ErsatzTV.Infrastructure/Search/LowercaseKeywordAnalyzer.cs
  5. 16
      ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs
  6. 3
      ErsatzTV.Infrastructure/Search/Models/ElasticSearchItem.cs
  7. 4
      ErsatzTV.Infrastructure/Search/SearchQueryParser.cs
  8. 1
      ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/MovieNfoReaderTests.cs
  9. 12
      ErsatzTV.Scanner/Core/Metadata/LocalMetadataProvider.cs
  10. 26
      ErsatzTV.Scanner/Core/Metadata/Nfo/MovieNfo.cs
  11. 7
      ErsatzTV.Scanner/Core/Metadata/Nfo/MovieNfoReader.cs

2
CHANGELOG.md

@ -101,6 +101,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -101,6 +101,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- `Not So Great (Part Three)`
- Add Trakt List option `Auto Refresh` to automatically update list from trakt.tv once each day
- Add Trakt List option `Generate Playlist` to automatically generate ETV Playlist from matched Trakt List items
- Read `country` field from movie NFO files and include in search index as `country`
### Changed
- Allow `Other Video` libraries and `Image` libraries to use the same folders
@ -143,6 +144,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -143,6 +144,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix bug where playout mode `Multiple` would ignore fixed start time
- Fix block playout EPG generation to use `XMLTV Time Zone` setting
- Fix adding "official" Trakt lists
- Fix searching for `collection` names with spaces or other special characters, e.g. `collection:"Movies - Action"`
## [25.2.0] - 2025-06-24
### Added

1
ErsatzTV.Core/Domain/Metadata/Tag.cs

@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
public class Tag
{
public static readonly string PlexNetworkTypeId = "319";
public static readonly string NfoCountryTypeId = "nfo/country";
public int Id { get; set; }
public string Name { get; set; }

8
ErsatzTV.Infrastructure/Search/ElasticSearchIndex.cs

@ -320,8 +320,12 @@ public class ElasticSearchIndex : ISearchIndex @@ -320,8 +320,12 @@ public class ElasticSearchIndex : ISearchIndex
AddedDate = GetAddedDate(metadata.DateAdded),
Plot = metadata.Plot ?? string.Empty,
Genre = metadata.Genres.Map(g => g.Name).ToList(),
Tag = metadata.Tags.Map(t => t.Name).ToList(),
TagFull = metadata.Tags.Map(t => t.Name).ToList(),
Tag = metadata.Tags.Where(t => string.IsNullOrWhiteSpace(t.ExternalTypeId)).Map(t => t.Name)
.ToList(),
TagFull = metadata.Tags.Where(t => string.IsNullOrWhiteSpace(t.ExternalTypeId)).Map(t => t.Name)
.ToList(),
Country = metadata.Tags.Where(t => t.ExternalTypeId == Tag.NfoCountryTypeId).Map(t => t.Name)
.ToList(),
Studio = metadata.Studios.Map(s => s.Name).ToList(),
Actor = metadata.Actors.Map(a => a.Name).ToList(),
Director = metadata.Directors.Map(d => d.Name).ToList(),

15
ErsatzTV.Infrastructure/Search/LowercaseKeywordAnalyzer.cs

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Core;
using Lucene.Net.Util;
namespace ErsatzTV.Infrastructure.Search;
public sealed class LowercaseKeywordAnalyzer(LuceneVersion matchVersion) : Analyzer
{
protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
{
Tokenizer tokenizer = new KeywordTokenizer(reader);
TokenStream result = new LowerCaseFilter(matchVersion, tokenizer);
return new TokenStreamComponents(tokenizer, result);
}
}

16
ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs

@ -35,6 +35,7 @@ public sealed class LuceneSearchIndex : ISearchIndex @@ -35,6 +35,7 @@ public sealed class LuceneSearchIndex : ISearchIndex
internal const string GenreField = "genre";
internal const string TagField = "tag";
internal const string TagFullField = "tag_full";
internal const string CountryField = "country";
internal const string PlotField = "plot";
internal const string LibraryNameField = "library_name";
internal const string LibraryIdField = "library_id";
@ -116,7 +117,7 @@ public sealed class LuceneSearchIndex : ISearchIndex @@ -116,7 +117,7 @@ public sealed class LuceneSearchIndex : ISearchIndex
return Task.FromResult(directoryExists && fileExists);
}
public int Version => 47;
public int Version => 48;
public async Task<bool> Initialize(
ILocalFileSystem localFileSystem,
@ -473,8 +474,15 @@ public sealed class LuceneSearchIndex : ISearchIndex @@ -473,8 +474,15 @@ public sealed class LuceneSearchIndex : ISearchIndex
foreach (Tag tag in metadata.Tags)
{
doc.Add(new TextField(TagField, tag.Name, Field.Store.NO));
doc.Add(new StringField(TagFullField, tag.Name, Field.Store.NO));
if (tag.ExternalTypeId == Tag.NfoCountryTypeId)
{
doc.Add(new TextField(CountryField, tag.Name, Field.Store.NO));
}
else
{
doc.Add(new TextField(TagField, tag.Name, Field.Store.NO));
doc.Add(new StringField(TagFullField, tag.Name, Field.Store.NO));
}
}
foreach (Studio studio in metadata.Studios)
@ -1465,7 +1473,7 @@ public sealed class LuceneSearchIndex : ISearchIndex @@ -1465,7 +1473,7 @@ public sealed class LuceneSearchIndex : ISearchIndex
{
foreach (Collection collection in collections)
{
doc.Add(new StringField(CollectionField, collection.Name.ToLowerInvariant(), Field.Store.NO));
doc.Add(new TextField(CollectionField, collection.Name.ToLowerInvariant(), Field.Store.NO));
}
}

3
ErsatzTV.Infrastructure/Search/Models/ElasticSearchItem.cs

@ -88,6 +88,9 @@ public class ElasticSearchItem : MinimalElasticSearchItem @@ -88,6 +88,9 @@ public class ElasticSearchItem : MinimalElasticSearchItem
[JsonPropertyName(LuceneSearchIndex.TagFullField)]
public List<string> TagFull { get; set; }
[JsonPropertyName(LuceneSearchIndex.CountryField)]
public List<string> Country { get; set; }
[JsonPropertyName(LuceneSearchIndex.StudioField)]
public List<string> Studio { get; set; }

4
ErsatzTV.Infrastructure/Search/SearchQueryParser.cs

@ -19,6 +19,7 @@ public partial class SearchQueryParser(ISmartCollectionCache smartCollectionCach @@ -19,6 +19,7 @@ public partial class SearchQueryParser(ISmartCollectionCache smartCollectionCach
{
using var defaultAnalyzer = new CustomAnalyzer(LuceneSearchIndex.AppLuceneVersion);
using var keywordAnalyzer = new KeywordAnalyzer();
using var lowercaseKeywordAnalyzer = new LowercaseKeywordAnalyzer(LuceneSearchIndex.AppLuceneVersion);
var customAnalyzers = new Dictionary<string, Analyzer>
{
// StringField should use KeywordAnalyzer
@ -38,7 +39,8 @@ public partial class SearchQueryParser(ISmartCollectionCache smartCollectionCach @@ -38,7 +39,8 @@ public partial class SearchQueryParser(ISmartCollectionCache smartCollectionCach
{ LuceneSearchIndex.VideoCodecField, keywordAnalyzer },
{ LuceneSearchIndex.VideoDynamicRangeField, keywordAnalyzer },
{ LuceneSearchIndex.TagFullField, keywordAnalyzer },
{ LuceneSearchIndex.CollectionField, keywordAnalyzer },
{ LuceneSearchIndex.CollectionField, lowercaseKeywordAnalyzer },
{ LuceneSearchIndex.PlotField, new StandardAnalyzer(LuceneSearchIndex.AppLuceneVersion) }
};

1
ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/MovieNfoReaderTests.cs

@ -191,6 +191,7 @@ https://www.themoviedb.org/movie/11-star-wars")); @@ -191,6 +191,7 @@ https://www.themoviedb.org/movie/11-star-wars"));
// nfo.Tagline.ShouldBeNullOrEmpty();
nfo.Genres.ShouldBeEquivalentTo(new List<string> { "SuperHero" });
nfo.Tags.ShouldBeEquivalentTo(new List<string> { "TV Recording" });
nfo.Countries.ShouldBeEquivalentTo(new List<string> { "USA" });
nfo.Studios.ShouldBeEquivalentTo(new List<string> { "Warner Bros. Pictures" });
nfo.Actors.ShouldBeEquivalentTo(
new List<ActorNfo>

12
ErsatzTV.Scanner/Core/Metadata/LocalMetadataProvider.cs

@ -1333,6 +1333,16 @@ public class LocalMetadataProvider : ILocalMetadataProvider @@ -1333,6 +1333,16 @@ public class LocalMetadataProvider : ILocalMetadataProvider
releaseDate = premiered;
}
var tags = nfo.Tags.Map(t => new Tag { Name = t }).ToList();
foreach (string country in nfo.Countries)
{
tags.Add(new Tag
{
Name = country,
ExternalTypeId = Tag.NfoCountryTypeId
});
}
return new MovieMetadata
{
MetadataKind = MetadataKind.Sidecar,
@ -1347,7 +1357,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider @@ -1347,7 +1357,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider
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(),
Tags = tags,
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(),

26
ErsatzTV.Scanner/Core/Metadata/Nfo/MovieNfo.cs

@ -2,17 +2,6 @@ @@ -2,17 +2,6 @@
public class MovieNfo
{
public MovieNfo()
{
Genres = new List<string>();
Tags = new List<string>();
Studios = new List<string>();
Actors = new List<ActorNfo>();
Writers = new List<string>();
Directors = new List<string>();
UniqueIds = new List<UniqueIdNfo>();
}
public string? Title { get; set; }
public string? SortTitle { get; set; }
public string? Outline { get; set; }
@ -23,11 +12,12 @@ public class MovieNfo @@ -23,11 +12,12 @@ public class MovieNfo
public string? Plot { get; set; }
// public string? Tagline { get; set; }
public List<string> Genres { get; }
public List<string> Tags { get; }
public List<string> Studios { get; }
public List<ActorNfo> Actors { get; }
public List<string> Writers { get; }
public List<string> Directors { get; }
public List<UniqueIdNfo> UniqueIds { get; }
public List<string> Genres { get; } = [];
public List<string> Tags { get; } = [];
public List<string> Studios { get; } = [];
public List<ActorNfo> Actors { get; } = [];
public List<string> Writers { get; } = [];
public List<string> Directors { get; } = [];
public List<UniqueIdNfo> UniqueIds { get; } = [];
public List<string> Countries { get; } = [];
}

7
ErsatzTV.Scanner/Core/Metadata/Nfo/MovieNfoReader.cs

@ -99,6 +99,13 @@ public class MovieNfoReader : NfoReader<MovieNfo>, IMovieNfoReader @@ -99,6 +99,13 @@ public class MovieNfoReader : NfoReader<MovieNfo>, IMovieNfoReader
case "tag":
await ReadStringContent(reader, nfo, (movie, tag) => movie.Tags.Add(tag), fileName);
break;
case "country":
await ReadStringContent(
reader,
nfo,
(movie, country) => movie.Countries.Add(country),
fileName);
break;
case "studio":
await ReadStringContent(
reader,

Loading…
Cancel
Save