|
|
|
@ -6,6 +6,8 @@ using ErsatzTV.Core.Interfaces.Repositories; |
|
|
|
using ErsatzTV.Core.Interfaces.Repositories.Caching; |
|
|
|
using ErsatzTV.Core.Interfaces.Repositories.Caching; |
|
|
|
using ErsatzTV.Core.Interfaces.Search; |
|
|
|
using ErsatzTV.Core.Interfaces.Search; |
|
|
|
using ErsatzTV.Core.Search; |
|
|
|
using ErsatzTV.Core.Search; |
|
|
|
|
|
|
|
using ErsatzTV.FFmpeg; |
|
|
|
|
|
|
|
using ErsatzTV.FFmpeg.Format; |
|
|
|
using LanguageExt.UnsafeValueAccess; |
|
|
|
using LanguageExt.UnsafeValueAccess; |
|
|
|
using Lucene.Net.Analysis; |
|
|
|
using Lucene.Net.Analysis; |
|
|
|
using Lucene.Net.Analysis.Core; |
|
|
|
using Lucene.Net.Analysis.Core; |
|
|
|
@ -20,6 +22,7 @@ using Lucene.Net.Store; |
|
|
|
using Lucene.Net.Util; |
|
|
|
using Lucene.Net.Util; |
|
|
|
using Microsoft.Extensions.Logging; |
|
|
|
using Microsoft.Extensions.Logging; |
|
|
|
using Directory = System.IO.Directory; |
|
|
|
using Directory = System.IO.Directory; |
|
|
|
|
|
|
|
using MediaStream = ErsatzTV.Core.Domain.MediaStream; |
|
|
|
using Query = Lucene.Net.Search.Query; |
|
|
|
using Query = Lucene.Net.Search.Query; |
|
|
|
|
|
|
|
|
|
|
|
namespace ErsatzTV.Infrastructure.Search; |
|
|
|
namespace ErsatzTV.Infrastructure.Search; |
|
|
|
@ -56,6 +59,8 @@ public sealed class SearchIndex : ISearchIndex |
|
|
|
private const string ShowGenreField = "show_genre"; |
|
|
|
private const string ShowGenreField = "show_genre"; |
|
|
|
private const string ShowTagField = "show_tag"; |
|
|
|
private const string ShowTagField = "show_tag"; |
|
|
|
private const string MetadataKindField = "metadata_kind"; |
|
|
|
private const string MetadataKindField = "metadata_kind"; |
|
|
|
|
|
|
|
private const string VideoCodecField = "video_codec"; |
|
|
|
|
|
|
|
private const string VideoDynamicRange = "video_dynamic_range"; |
|
|
|
|
|
|
|
|
|
|
|
internal const string MinutesField = "minutes"; |
|
|
|
internal const string MinutesField = "minutes"; |
|
|
|
internal const string HeightField = "height"; |
|
|
|
internal const string HeightField = "height"; |
|
|
|
@ -64,6 +69,7 @@ public sealed class SearchIndex : ISearchIndex |
|
|
|
internal const string EpisodeNumberField = "episode_number"; |
|
|
|
internal const string EpisodeNumberField = "episode_number"; |
|
|
|
internal const string AddedDateField = "added_date"; |
|
|
|
internal const string AddedDateField = "added_date"; |
|
|
|
internal const string ReleaseDateField = "release_date"; |
|
|
|
internal const string ReleaseDateField = "release_date"; |
|
|
|
|
|
|
|
internal const string VideoBitDepthField = "video_bit_depth"; |
|
|
|
|
|
|
|
|
|
|
|
public const string MovieType = "movie"; |
|
|
|
public const string MovieType = "movie"; |
|
|
|
public const string ShowType = "show"; |
|
|
|
public const string ShowType = "show"; |
|
|
|
@ -73,7 +79,7 @@ public sealed class SearchIndex : ISearchIndex |
|
|
|
public const string EpisodeType = "episode"; |
|
|
|
public const string EpisodeType = "episode"; |
|
|
|
public const string OtherVideoType = "other_video"; |
|
|
|
public const string OtherVideoType = "other_video"; |
|
|
|
public const string SongType = "song"; |
|
|
|
public const string SongType = "song"; |
|
|
|
|
|
|
|
|
|
|
|
private readonly List<CultureInfo> _cultureInfos; |
|
|
|
private readonly List<CultureInfo> _cultureInfos; |
|
|
|
|
|
|
|
|
|
|
|
private readonly ILogger<SearchIndex> _logger; |
|
|
|
private readonly ILogger<SearchIndex> _logger; |
|
|
|
@ -89,7 +95,7 @@ public sealed class SearchIndex : ISearchIndex |
|
|
|
_initialized = false; |
|
|
|
_initialized = false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public int Version => 33; |
|
|
|
public int Version => 34; |
|
|
|
|
|
|
|
|
|
|
|
public async Task<bool> Initialize( |
|
|
|
public async Task<bool> Initialize( |
|
|
|
ILocalFileSystem localFileSystem, |
|
|
|
ILocalFileSystem localFileSystem, |
|
|
|
@ -382,14 +388,7 @@ public sealed class SearchIndex : ISearchIndex |
|
|
|
|
|
|
|
|
|
|
|
await AddLanguages(searchRepository, doc, movie.MediaVersions); |
|
|
|
await AddLanguages(searchRepository, doc, movie.MediaVersions); |
|
|
|
|
|
|
|
|
|
|
|
foreach (MediaVersion version in movie.MediaVersions.HeadOrNone()) |
|
|
|
AddStatistics(doc, movie.MediaVersions); |
|
|
|
{ |
|
|
|
|
|
|
|
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)) |
|
|
|
if (!string.IsNullOrWhiteSpace(metadata.ContentRating)) |
|
|
|
{ |
|
|
|
{ |
|
|
|
@ -752,14 +751,7 @@ public sealed class SearchIndex : ISearchIndex |
|
|
|
|
|
|
|
|
|
|
|
await AddLanguages(searchRepository, doc, musicVideo.MediaVersions); |
|
|
|
await AddLanguages(searchRepository, doc, musicVideo.MediaVersions); |
|
|
|
|
|
|
|
|
|
|
|
foreach (MediaVersion version in musicVideo.MediaVersions.HeadOrNone()) |
|
|
|
AddStatistics(doc, musicVideo.MediaVersions); |
|
|
|
{ |
|
|
|
|
|
|
|
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) |
|
|
|
if (metadata.ReleaseDate.HasValue) |
|
|
|
{ |
|
|
|
{ |
|
|
|
@ -891,14 +883,7 @@ public sealed class SearchIndex : ISearchIndex |
|
|
|
|
|
|
|
|
|
|
|
await AddLanguages(searchRepository, doc, episode.MediaVersions); |
|
|
|
await AddLanguages(searchRepository, doc, episode.MediaVersions); |
|
|
|
|
|
|
|
|
|
|
|
foreach (MediaVersion version in episode.MediaVersions.HeadOrNone()) |
|
|
|
AddStatistics(doc, episode.MediaVersions); |
|
|
|
{ |
|
|
|
|
|
|
|
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) |
|
|
|
if (metadata.ReleaseDate.HasValue) |
|
|
|
{ |
|
|
|
{ |
|
|
|
@ -988,14 +973,7 @@ public sealed class SearchIndex : ISearchIndex |
|
|
|
|
|
|
|
|
|
|
|
await AddLanguages(searchRepository, doc, otherVideo.MediaVersions); |
|
|
|
await AddLanguages(searchRepository, doc, otherVideo.MediaVersions); |
|
|
|
|
|
|
|
|
|
|
|
foreach (MediaVersion version in otherVideo.MediaVersions.HeadOrNone()) |
|
|
|
AddStatistics(doc, otherVideo.MediaVersions); |
|
|
|
{ |
|
|
|
|
|
|
|
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)) |
|
|
|
if (!string.IsNullOrWhiteSpace(metadata.ContentRating)) |
|
|
|
{ |
|
|
|
{ |
|
|
|
@ -1089,11 +1067,7 @@ public sealed class SearchIndex : ISearchIndex |
|
|
|
|
|
|
|
|
|
|
|
await AddLanguages(searchRepository, doc, song.MediaVersions); |
|
|
|
await AddLanguages(searchRepository, doc, song.MediaVersions); |
|
|
|
|
|
|
|
|
|
|
|
foreach (MediaVersion version in song.MediaVersions.HeadOrNone()) |
|
|
|
AddStatistics(doc, song.MediaVersions); |
|
|
|
{ |
|
|
|
|
|
|
|
doc.Add( |
|
|
|
|
|
|
|
new Int32Field(MinutesField, (int)Math.Ceiling(version.Duration.TotalMinutes), Field.Store.NO)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
doc.Add(new StringField(AddedDateField, metadata.DateAdded.ToString("yyyyMMdd"), Field.Store.NO)); |
|
|
|
doc.Add(new StringField(AddedDateField, metadata.DateAdded.ToString("yyyyMMdd"), Field.Store.NO)); |
|
|
|
|
|
|
|
|
|
|
|
@ -1152,6 +1126,42 @@ public sealed class SearchIndex : ISearchIndex |
|
|
|
|
|
|
|
|
|
|
|
return query; |
|
|
|
return query; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void AddStatistics(Document doc, List<MediaVersion> mediaVersions) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
foreach (MediaVersion version in mediaVersions) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
doc.Add(new Int32Field(MinutesField, (int)Math.Ceiling(version.Duration.TotalMinutes), Field.Store.NO)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (version.Streams.Any(s => s.MediaStreamKind == MediaStreamKind.Video)) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
doc.Add(new Int32Field(HeightField, version.Height, Field.Store.NO)); |
|
|
|
|
|
|
|
doc.Add(new Int32Field(WidthField, version.Width, Field.Store.NO)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
foreach (MediaStream videoStream in version.Streams.Filter(s => s.MediaStreamKind == MediaStreamKind.Video)) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
doc.Add(new StringField(VideoCodecField, videoStream.Codec, Field.Store.NO)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Option<IPixelFormat> maybePixelFormat = |
|
|
|
|
|
|
|
AvailablePixelFormats.ForPixelFormat(videoStream.PixelFormat, null); |
|
|
|
|
|
|
|
foreach (IPixelFormat pixelFormat in maybePixelFormat) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
doc.Add(new Int32Field(VideoBitDepthField, pixelFormat.BitDepth, Field.Store.NO)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var colorParams = new ColorParams( |
|
|
|
|
|
|
|
videoStream.ColorRange, |
|
|
|
|
|
|
|
videoStream.ColorSpace, |
|
|
|
|
|
|
|
videoStream.ColorTransfer, |
|
|
|
|
|
|
|
videoStream.ColorPrimaries); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
string dynamicRange = colorParams.IsHdr ? "hdr" : "sdr"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
doc.Add(new TextField(VideoDynamicRange, dynamicRange, Field.Store.NO)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static void AddMetadataGuids(Metadata metadata, Document doc) |
|
|
|
private static void AddMetadataGuids(Metadata metadata, Document doc) |
|
|
|
{ |
|
|
|
{ |
|
|
|
|