add content rating (#218)
* add new columns * store local content ratings * display and search content ratings * add content_rating to search docs * sync content rating from jellyfin, emby, plex * force sync content rating for all libraries * code cleanup
This commit is contained in:
@@ -105,6 +105,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
// metadata
|
||||
ShowMetadata metadata = existing.ShowMetadata.Head();
|
||||
ShowMetadata incomingMetadata = show.ShowMetadata.Head();
|
||||
metadata.MetadataKind = incomingMetadata.MetadataKind;
|
||||
metadata.ContentRating = incomingMetadata.ContentRating;
|
||||
metadata.Title = incomingMetadata.Title;
|
||||
metadata.SortTitle = incomingMetadata.SortTitle;
|
||||
metadata.Plot = incomingMetadata.Plot;
|
||||
|
||||
@@ -105,6 +105,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
// metadata
|
||||
ShowMetadata metadata = existing.ShowMetadata.Head();
|
||||
ShowMetadata incomingMetadata = show.ShowMetadata.Head();
|
||||
metadata.MetadataKind = incomingMetadata.MetadataKind;
|
||||
metadata.ContentRating = incomingMetadata.ContentRating;
|
||||
metadata.Title = incomingMetadata.Title;
|
||||
metadata.SortTitle = incomingMetadata.SortTitle;
|
||||
metadata.Plot = incomingMetadata.Plot;
|
||||
|
||||
@@ -228,6 +228,26 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
@"UPDATE EpisodeMetadata SET DateUpdated = @DateUpdated WHERE Id = @Id",
|
||||
new { DateUpdated = dateUpdated, metadata.Id }).ToUnit();
|
||||
|
||||
public Task<Unit> MarkAsExternal(ShowMetadata metadata) =>
|
||||
_dbConnection.ExecuteAsync(
|
||||
@"UPDATE ShowMetadata SET MetadataKind = @Kind WHERE Id = @Id",
|
||||
new { metadata.Id, Kind = (int) MetadataKind.External }).ToUnit();
|
||||
|
||||
public Task<Unit> SetContentRating(ShowMetadata metadata, string contentRating) =>
|
||||
_dbConnection.ExecuteAsync(
|
||||
@"UPDATE ShowMetadata SET ContentRating = @ContentRating WHERE Id = @Id",
|
||||
new { metadata.Id, ContentRating = contentRating }).ToUnit();
|
||||
|
||||
public Task<Unit> MarkAsExternal(MovieMetadata metadata) =>
|
||||
_dbConnection.ExecuteAsync(
|
||||
@"UPDATE MovieMetadata SET MetadataKind = @Kind WHERE Id = @Id",
|
||||
new { metadata.Id, Kind = (int) MetadataKind.External }).ToUnit();
|
||||
|
||||
public Task<Unit> SetContentRating(MovieMetadata metadata, string contentRating) =>
|
||||
_dbConnection.ExecuteAsync(
|
||||
@"UPDATE MovieMetadata SET ContentRating = @ContentRating WHERE Id = @Id",
|
||||
new { metadata.Id, ContentRating = contentRating }).ToUnit();
|
||||
|
||||
public Task<bool> RemoveGenre(Genre genre) =>
|
||||
_dbConnection.ExecuteAsync("DELETE FROM Genre WHERE Id = @GenreId", new { GenreId = genre.Id })
|
||||
.Map(result => result > 0);
|
||||
|
||||
@@ -327,6 +327,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
// metadata
|
||||
MovieMetadata metadata = existing.MovieMetadata.Head();
|
||||
MovieMetadata incomingMetadata = movie.MovieMetadata.Head();
|
||||
metadata.MetadataKind = incomingMetadata.MetadataKind;
|
||||
metadata.ContentRating = incomingMetadata.ContentRating;
|
||||
metadata.Title = incomingMetadata.Title;
|
||||
metadata.SortTitle = incomingMetadata.SortTitle;
|
||||
metadata.Plot = incomingMetadata.Plot;
|
||||
@@ -528,6 +530,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
// metadata
|
||||
MovieMetadata metadata = existing.MovieMetadata.Head();
|
||||
MovieMetadata incomingMetadata = movie.MovieMetadata.Head();
|
||||
metadata.MetadataKind = incomingMetadata.MetadataKind;
|
||||
metadata.ContentRating = incomingMetadata.ContentRating;
|
||||
metadata.Title = incomingMetadata.Title;
|
||||
metadata.SortTitle = incomingMetadata.SortTitle;
|
||||
metadata.Plot = incomingMetadata.Plot;
|
||||
|
||||
@@ -230,12 +230,14 @@ namespace ErsatzTV.Infrastructure.Emby
|
||||
|
||||
var metadata = new MovieMetadata
|
||||
{
|
||||
MetadataKind = MetadataKind.External,
|
||||
Title = item.Name,
|
||||
SortTitle = _fallbackMetadataProvider.GetSortTitle(item.Name),
|
||||
Plot = item.Overview,
|
||||
Year = item.ProductionYear,
|
||||
Tagline = Optional(item.Taglines).Flatten().HeadOrNone().IfNone(string.Empty),
|
||||
DateAdded = dateAdded,
|
||||
ContentRating = item.OfficialRating,
|
||||
Genres = Optional(item.Genres).Flatten().Map(g => new Genre { Name = g }).ToList(),
|
||||
Tags = Optional(item.Tags).Flatten().Map(t => new Tag { Name = t }).ToList(),
|
||||
Studios = Optional(item.Studios).Flatten().Map(s => new Studio { Name = s.Name }).ToList(),
|
||||
@@ -324,12 +326,14 @@ namespace ErsatzTV.Infrastructure.Emby
|
||||
|
||||
var metadata = new ShowMetadata
|
||||
{
|
||||
MetadataKind = MetadataKind.External,
|
||||
Title = item.Name,
|
||||
SortTitle = _fallbackMetadataProvider.GetSortTitle(item.Name),
|
||||
Plot = item.Overview,
|
||||
Year = item.ProductionYear,
|
||||
Tagline = Optional(item.Taglines).Flatten().HeadOrNone().IfNone(string.Empty),
|
||||
DateAdded = dateAdded,
|
||||
ContentRating = item.OfficialRating,
|
||||
Genres = Optional(item.Genres).Flatten().Map(g => new Genre { Name = g }).ToList(),
|
||||
Tags = Optional(item.Tags).Flatten().Map(t => new Tag { Name = t }).ToList(),
|
||||
Studios = Optional(item.Studios).Flatten().Map(s => new Studio { Name = s.Name }).ToList(),
|
||||
@@ -393,6 +397,7 @@ namespace ErsatzTV.Infrastructure.Emby
|
||||
|
||||
var metadata = new SeasonMetadata
|
||||
{
|
||||
MetadataKind = MetadataKind.External,
|
||||
Title = item.Name,
|
||||
SortTitle = _fallbackMetadataProvider.GetSortTitle(item.Name),
|
||||
Year = item.ProductionYear,
|
||||
@@ -498,6 +503,7 @@ namespace ErsatzTV.Infrastructure.Emby
|
||||
|
||||
var metadata = new EpisodeMetadata
|
||||
{
|
||||
MetadataKind = MetadataKind.External,
|
||||
Title = item.Name,
|
||||
SortTitle = _fallbackMetadataProvider.GetSortTitle(item.Name),
|
||||
Plot = item.Overview,
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace ErsatzTV.Infrastructure.Emby
|
||||
string parentId,
|
||||
[Query]
|
||||
string fields =
|
||||
"Path,Genres,Tags,DateCreated,Etag,Overview,Taglines,Studios,People,ProductionYear,PremiereDate,MediaSources",
|
||||
"Path,Genres,Tags,DateCreated,Etag,Overview,Taglines,Studios,People,ProductionYear,PremiereDate,MediaSources,OfficialRating",
|
||||
[Query]
|
||||
string includeItemTypes = "Movie");
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace ErsatzTV.Infrastructure.Emby
|
||||
string parentId,
|
||||
[Query]
|
||||
string fields =
|
||||
"Path,Genres,Tags,DateCreated,Etag,Overview,Taglines,Studios,People,ProductionYear,PremiereDate,MediaSources",
|
||||
"Path,Genres,Tags,DateCreated,Etag,Overview,Taglines,Studios,People,ProductionYear,PremiereDate,MediaSources,OfficialRating",
|
||||
[Query]
|
||||
string includeItemTypes = "Series");
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace ErsatzTV.Infrastructure.Emby.Models
|
||||
public string Id { get; set; }
|
||||
public string Etag { get; set; }
|
||||
public string Path { get; set; }
|
||||
public string OfficialRating { get; set; }
|
||||
public DateTimeOffset DateCreated { get; set; }
|
||||
public long RunTimeTicks { get; set; }
|
||||
public List<string> Genres { get; set; }
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace ErsatzTV.Infrastructure.Jellyfin
|
||||
[Query]
|
||||
string parentId,
|
||||
[Query]
|
||||
string fields = "Path,Genres,Tags,DateCreated,Etag,Overview,Taglines,Studios,People",
|
||||
string fields = "Path,Genres,Tags,DateCreated,Etag,Overview,Taglines,Studios,People,OfficialRating",
|
||||
[Query]
|
||||
string includeItemTypes = "Movie");
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace ErsatzTV.Infrastructure.Jellyfin
|
||||
[Query]
|
||||
string parentId,
|
||||
[Query]
|
||||
string fields = "Path,Genres,Tags,DateCreated,Etag,Overview,Taglines,Studios,People",
|
||||
string fields = "Path,Genres,Tags,DateCreated,Etag,Overview,Taglines,Studios,People,OfficialRating",
|
||||
[Query]
|
||||
string includeItemTypes = "Series");
|
||||
|
||||
|
||||
@@ -276,12 +276,14 @@ namespace ErsatzTV.Infrastructure.Jellyfin
|
||||
|
||||
var metadata = new MovieMetadata
|
||||
{
|
||||
MetadataKind = MetadataKind.External,
|
||||
Title = item.Name,
|
||||
SortTitle = _fallbackMetadataProvider.GetSortTitle(item.Name),
|
||||
Plot = item.Overview,
|
||||
Year = item.ProductionYear,
|
||||
Tagline = Optional(item.Taglines).Flatten().HeadOrNone().IfNone(string.Empty),
|
||||
DateAdded = dateAdded,
|
||||
ContentRating = item.OfficialRating,
|
||||
Genres = Optional(item.Genres).Flatten().Map(g => new Genre { Name = g }).ToList(),
|
||||
Tags = Optional(item.Tags).Flatten().Map(t => new Tag { Name = t }).ToList(),
|
||||
Studios = Optional(item.Studios).Flatten().Map(s => new Studio { Name = s.Name }).ToList(),
|
||||
@@ -375,12 +377,14 @@ namespace ErsatzTV.Infrastructure.Jellyfin
|
||||
|
||||
var metadata = new ShowMetadata
|
||||
{
|
||||
MetadataKind = MetadataKind.External,
|
||||
Title = item.Name,
|
||||
SortTitle = _fallbackMetadataProvider.GetSortTitle(item.Name),
|
||||
Plot = item.Overview,
|
||||
Year = item.ProductionYear,
|
||||
Tagline = Optional(item.Taglines).Flatten().HeadOrNone().IfNone(string.Empty),
|
||||
DateAdded = dateAdded,
|
||||
ContentRating = item.OfficialRating,
|
||||
Genres = Optional(item.Genres).Flatten().Map(g => new Genre { Name = g }).ToList(),
|
||||
Tags = Optional(item.Tags).Flatten().Map(t => new Tag { Name = t }).ToList(),
|
||||
Studios = Optional(item.Studios).Flatten().Map(s => new Studio { Name = s.Name }).ToList(),
|
||||
@@ -449,6 +453,7 @@ namespace ErsatzTV.Infrastructure.Jellyfin
|
||||
|
||||
var metadata = new SeasonMetadata
|
||||
{
|
||||
MetadataKind = MetadataKind.External,
|
||||
Title = item.Name,
|
||||
SortTitle = _fallbackMetadataProvider.GetSortTitle(item.Name),
|
||||
Year = item.ProductionYear,
|
||||
@@ -494,7 +499,7 @@ namespace ErsatzTV.Infrastructure.Jellyfin
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Error projecting Jellyfin show");
|
||||
_logger.LogWarning(ex, "Error projecting Jellyfin season");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
@@ -542,7 +547,7 @@ namespace ErsatzTV.Infrastructure.Jellyfin
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Error projecting Jellyfin movie");
|
||||
_logger.LogWarning(ex, "Error projecting Jellyfin episode");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
@@ -554,6 +559,7 @@ namespace ErsatzTV.Infrastructure.Jellyfin
|
||||
|
||||
var metadata = new EpisodeMetadata
|
||||
{
|
||||
MetadataKind = MetadataKind.External,
|
||||
Title = item.Name,
|
||||
SortTitle = _fallbackMetadataProvider.GetSortTitle(item.Name),
|
||||
Plot = item.Overview,
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace ErsatzTV.Infrastructure.Jellyfin.Models
|
||||
public string Id { get; set; }
|
||||
public string Etag { get; set; }
|
||||
public string Path { get; set; }
|
||||
public string OfficialRating { get; set; }
|
||||
public DateTimeOffset DateCreated { get; set; }
|
||||
public long RunTimeTicks { get; set; }
|
||||
public List<string> Genres { get; set; }
|
||||
|
||||
+2717
File diff suppressed because it is too large
Load Diff
+50
@@ -0,0 +1,50 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Migrations
|
||||
{
|
||||
public partial class Add_MovieMetadataShowMetadataContentRating : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// local and plex
|
||||
migrationBuilder.Sql("UPDATE MovieMetadata SET DateUpdated = '0001-01-01 00:00:00'");
|
||||
migrationBuilder.Sql("UPDATE ShowMetadata SET DateUpdated = '0001-01-01 00:00:00'");
|
||||
migrationBuilder.Sql(
|
||||
@"UPDATE LibraryFolder SET Etag = NULL WHERE LibraryPathId IN
|
||||
(SELECT LibraryPathId FROM LibraryPath LP
|
||||
INNER JOIN Library L on LP.LibraryId = L.Id
|
||||
WHERE L.MediaKind = 1)");
|
||||
|
||||
// emby
|
||||
migrationBuilder.Sql("UPDATE EmbyMovie SET Etag = NULL");
|
||||
migrationBuilder.Sql("UPDATE EmbyShow SET Etag = NULL");
|
||||
|
||||
// jellyfin
|
||||
migrationBuilder.Sql("UPDATE JellyfinMovie SET Etag = NULL");
|
||||
migrationBuilder.Sql("UPDATE JellyfinShow SET Etag = NULL");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
"ContentRating",
|
||||
"ShowMetadata",
|
||||
"TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
"ContentRating",
|
||||
"MovieMetadata",
|
||||
"TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
"ContentRating",
|
||||
"ShowMetadata");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
"ContentRating",
|
||||
"MovieMetadata");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -794,6 +794,9 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ContentRating")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@@ -1170,6 +1173,9 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ContentRating")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string ContentRating { get; set; }
|
||||
public string Summary { get; set; }
|
||||
public int Year { get; set; }
|
||||
public string Tagline { get; set; }
|
||||
|
||||
@@ -250,11 +250,13 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
|
||||
var metadata = new MovieMetadata
|
||||
{
|
||||
MetadataKind = MetadataKind.External,
|
||||
Title = response.Title,
|
||||
SortTitle = _fallbackMetadataProvider.GetSortTitle(response.Title),
|
||||
Plot = response.Summary,
|
||||
Year = response.Year,
|
||||
Tagline = response.Tagline,
|
||||
ContentRating = response.ContentRating,
|
||||
DateAdded = dateAdded,
|
||||
DateUpdated = lastWriteTime,
|
||||
Genres = Optional(response.Genre).Flatten().Map(g => new Genre { Name = g.Tag }).ToList(),
|
||||
@@ -398,11 +400,13 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
|
||||
var metadata = new ShowMetadata
|
||||
{
|
||||
MetadataKind = MetadataKind.External,
|
||||
Title = response.Title,
|
||||
SortTitle = _fallbackMetadataProvider.GetSortTitle(response.Title),
|
||||
Plot = response.Summary,
|
||||
Year = response.Year,
|
||||
Tagline = response.Tagline,
|
||||
ContentRating = response.ContentRating,
|
||||
DateAdded = dateAdded,
|
||||
DateUpdated = lastWriteTime,
|
||||
Genres = Optional(response.Genre).Flatten().Map(g => new Genre { Name = g.Tag }).ToList(),
|
||||
@@ -462,6 +466,7 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
|
||||
var metadata = new SeasonMetadata
|
||||
{
|
||||
MetadataKind = MetadataKind.External,
|
||||
Title = response.Title,
|
||||
SortTitle = _fallbackMetadataProvider.GetSortTitle(response.Title),
|
||||
Year = response.Year,
|
||||
@@ -518,6 +523,7 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
|
||||
var metadata = new EpisodeMetadata
|
||||
{
|
||||
MetadataKind = MetadataKind.External,
|
||||
Title = response.Title,
|
||||
SortTitle = _fallbackMetadataProvider.GetSortTitle(response.Title),
|
||||
Plot = response.Summary,
|
||||
|
||||
@@ -11,6 +11,9 @@ using ErsatzTV.Core.Interfaces.Search;
|
||||
using ErsatzTV.Core.Search;
|
||||
using LanguageExt;
|
||||
using LanguageExt.UnsafeValueAccess;
|
||||
using Lucene.Net.Analysis;
|
||||
using Lucene.Net.Analysis.Core;
|
||||
using Lucene.Net.Analysis.Miscellaneous;
|
||||
using Lucene.Net.Analysis.Standard;
|
||||
using Lucene.Net.Documents;
|
||||
using Lucene.Net.Index;
|
||||
@@ -45,6 +48,7 @@ namespace ErsatzTV.Infrastructure.Search
|
||||
private const string StyleField = "style";
|
||||
private const string MoodField = "mood";
|
||||
private const string ActorField = "actor";
|
||||
private const string ContentRatingField = "content_rating";
|
||||
|
||||
private const string MovieType = "movie";
|
||||
private const string ShowType = "show";
|
||||
@@ -65,7 +69,7 @@ namespace ErsatzTV.Infrastructure.Search
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
public int Version => 9;
|
||||
public int Version => 10;
|
||||
|
||||
public Task<bool> Initialize(ILocalFileSystem localFileSystem)
|
||||
{
|
||||
@@ -162,9 +166,14 @@ namespace ErsatzTV.Infrastructure.Search
|
||||
var searcher = new IndexSearcher(reader);
|
||||
int hitsLimit = limit == 0 ? searcher.IndexReader.MaxDoc : skip + limit;
|
||||
using var analyzer = new StandardAnalyzer(AppLuceneVersion);
|
||||
var customAnalyzers = new Dictionary<string, Analyzer>
|
||||
{
|
||||
{ ContentRatingField, new KeywordAnalyzer() }
|
||||
};
|
||||
using var analyzerWrapper = new PerFieldAnalyzerWrapper(analyzer, customAnalyzers);
|
||||
QueryParser parser = !string.IsNullOrWhiteSpace(searchField)
|
||||
? new QueryParser(AppLuceneVersion, searchField, analyzer)
|
||||
: new MultiFieldQueryParser(AppLuceneVersion, new[] { TitleField }, analyzer);
|
||||
? new QueryParser(AppLuceneVersion, searchField, analyzerWrapper)
|
||||
: new MultiFieldQueryParser(AppLuceneVersion, new[] { TitleField }, analyzerWrapper);
|
||||
parser.AllowLeadingWildcard = true;
|
||||
Query query = ParseQuery(searchQuery, parser);
|
||||
var filter = new DuplicateFilter(TitleAndYearField);
|
||||
@@ -266,6 +275,15 @@ namespace ErsatzTV.Infrastructure.Search
|
||||
|
||||
AddLanguages(doc, movie.MediaVersions);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(metadata.ContentRating))
|
||||
{
|
||||
foreach (string contentRating in (metadata.ContentRating ?? string.Empty).Split("/")
|
||||
.Map(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)))
|
||||
{
|
||||
doc.Add(new StringField(ContentRatingField, contentRating, Field.Store.NO));
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata.ReleaseDate.HasValue)
|
||||
{
|
||||
doc.Add(
|
||||
@@ -368,6 +386,15 @@ namespace ErsatzTV.Infrastructure.Search
|
||||
doc.Add(new TextField(LanguageField, cultureInfo.EnglishName, Field.Store.NO));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(metadata.ContentRating))
|
||||
{
|
||||
foreach (string contentRating in (metadata.ContentRating ?? string.Empty).Split("/")
|
||||
.Map(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)))
|
||||
{
|
||||
doc.Add(new StringField(ContentRatingField, contentRating, Field.Store.NO));
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata.ReleaseDate.HasValue)
|
||||
{
|
||||
doc.Add(
|
||||
|
||||
Reference in New Issue
Block a user