Browse Source

add movie directors and writers (#219)

pull/220/head
Jason Dove 5 years ago committed by GitHub
parent
commit
0bbed69e85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      ErsatzTV.Application/Movies/Mapper.cs
  2. 4
      ErsatzTV.Application/Movies/MovieViewModel.cs
  3. 8
      ErsatzTV.Core/Domain/Metadata/Director.cs
  4. 6
      ErsatzTV.Core/Domain/Metadata/MovieMetadata.cs
  5. 8
      ErsatzTV.Core/Domain/Metadata/Writer.cs
  6. 4
      ErsatzTV.Core/Interfaces/Repositories/IMovieRepository.cs
  7. 44
      ErsatzTV.Core/Metadata/LocalMetadataProvider.cs
  8. 6
      ErsatzTV.Core/Metadata/Nfo/MovieNfo.cs
  9. 44
      ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs
  10. 11
      ErsatzTV.Infrastructure/Data/Configurations/Metadata/DirectorConfiguration.cs
  11. 8
      ErsatzTV.Infrastructure/Data/Configurations/Metadata/MovieMetadataConfiguration.cs
  12. 11
      ErsatzTV.Infrastructure/Data/Configurations/Metadata/Writer.cs
  13. 13
      ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs
  14. 100
      ErsatzTV.Infrastructure/Data/Repositories/MovieRepository.cs
  15. 4
      ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs
  16. 33
      ErsatzTV.Infrastructure/Emby/EmbyApiClient.cs
  17. 33
      ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs
  18. 2775
      ErsatzTV.Infrastructure/Migrations/20210527122609_Add_MovieMetadataDirectorsWriters.Designer.cs
  19. 83
      ErsatzTV.Infrastructure/Migrations/20210527122609_Add_MovieMetadataDirectorsWriters.cs
  20. 66
      ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs
  21. 7
      ErsatzTV.Infrastructure/Plex/Models/PlexDirectorResponse.cs
  22. 2
      ErsatzTV.Infrastructure/Plex/Models/PlexMetadataResponse.cs
  23. 7
      ErsatzTV.Infrastructure/Plex/Models/PlexWriterResponse.mcs.cs
  24. 4
      ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs
  25. 14
      ErsatzTV.Infrastructure/Search/SearchIndex.cs
  26. 28
      ErsatzTV/Pages/Movie.razor

4
ErsatzTV.Application/Movies/Mapper.cs

@ -31,7 +31,9 @@ namespace ErsatzTV.Application.Movies @@ -31,7 +31,9 @@ namespace ErsatzTV.Application.Movies
LanguagesForMovie(movie),
metadata.Actors.OrderBy(a => a.Order).ThenBy(a => a.Id)
.Map(a => MediaCards.Mapper.ProjectToViewModel(a, maybeJellyfin, maybeEmby))
.ToList())
.ToList(),
metadata.Directors.Map(d => d.Name).ToList(),
metadata.Writers.Map(w => w.Name).ToList())
{
Poster = Artwork(metadata, ArtworkKind.Poster, maybeJellyfin, maybeEmby),
FanArt = Artwork(metadata, ArtworkKind.FanArt, maybeJellyfin, maybeEmby)

4
ErsatzTV.Application/Movies/MovieViewModel.cs

@ -13,7 +13,9 @@ namespace ErsatzTV.Application.Movies @@ -13,7 +13,9 @@ namespace ErsatzTV.Application.Movies
List<string> Studios,
List<string> ContentRatings,
List<CultureInfo> Languages,
List<ActorCardViewModel> Actors)
List<ActorCardViewModel> Actors,
List<string> Directors,
List<string> Writers)
{
public string Poster { get; set; }
public string FanArt { get; set; }

8
ErsatzTV.Core/Domain/Metadata/Director.cs

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
namespace ErsatzTV.Core.Domain
{
public class Director
{
public int Id { get; set; }
public string Name { get; set; }
}
}

6
ErsatzTV.Core/Domain/Metadata/MovieMetadata.cs

@ -1,4 +1,6 @@ @@ -1,4 +1,6 @@
namespace ErsatzTV.Core.Domain
using System.Collections.Generic;
namespace ErsatzTV.Core.Domain
{
public class MovieMetadata : Metadata
{
@ -8,5 +10,7 @@ @@ -8,5 +10,7 @@
public string Tagline { get; set; }
public int MovieId { get; set; }
public Movie Movie { get; set; }
public List<Director> Directors { get; set; }
public List<Writer> Writers { get; set; }
}
}

8
ErsatzTV.Core/Domain/Metadata/Writer.cs

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
namespace ErsatzTV.Core.Domain
{
public class Writer
{
public int Id { get; set; }
public string Name { get; set; }
}
}

4
ErsatzTV.Core/Interfaces/Repositories/IMovieRepository.cs

@ -33,5 +33,9 @@ namespace ErsatzTV.Core.Interfaces.Repositories @@ -33,5 +33,9 @@ namespace ErsatzTV.Core.Interfaces.Repositories
Task<List<int>> RemoveMissingEmbyMovies(EmbyLibrary library, List<string> movieIds);
Task<bool> AddEmby(EmbyMovie movie);
Task<Option<EmbyMovie>> UpdateEmby(EmbyMovie movie);
Task<bool> RemoveDirector(Director director);
Task<bool> AddDirector(MovieMetadata metadata, Director director);
Task<bool> RemoveWriter(Writer writer);
Task<bool> AddWriter(MovieMetadata metadata, Writer writer);
}
}

44
ErsatzTV.Core/Metadata/LocalMetadataProvider.cs

@ -260,6 +260,46 @@ namespace ErsatzTV.Core.Metadata @@ -260,6 +260,46 @@ namespace ErsatzTV.Core.Metadata
_movieRepository.AddStudio,
_movieRepository.AddActor);
foreach (Director director in existing.Directors
.Filter(g => metadata.Directors.All(g2 => g2.Name != g.Name)).ToList())
{
existing.Directors.Remove(director);
if (await _movieRepository.RemoveDirector(director))
{
updated = true;
}
}
foreach (Director director in metadata.Directors
.Filter(g => existing.Directors.All(g2 => g2.Name != g.Name)).ToList())
{
existing.Directors.Add(director);
if (await _movieRepository.AddDirector(existing, director))
{
updated = true;
}
}
foreach (Writer writer in existing.Writers
.Filter(g => metadata.Writers.All(g2 => g2.Name != g.Name)).ToList())
{
existing.Writers.Remove(writer);
if (await _movieRepository.RemoveWriter(writer))
{
updated = true;
}
}
foreach (Writer writer in metadata.Writers
.Filter(g => existing.Writers.All(g2 => g2.Name != g.Name)).ToList())
{
existing.Writers.Add(writer);
if (await _movieRepository.AddWriter(existing, writer))
{
updated = true;
}
}
return await _metadataRepository.Update(existing) || updated;
},
async () =>
@ -582,7 +622,9 @@ namespace ErsatzTV.Core.Metadata @@ -582,7 +622,9 @@ namespace ErsatzTV.Core.Metadata
Genres = nfo.Genres.Map(g => new Genre { Name = g }).ToList(),
Tags = nfo.Tags.Map(t => new Tag { Name = t }).ToList(),
Studios = nfo.Studios.Map(s => new Studio { Name = s }).ToList(),
Actors = Actors(nfo.Actors, dateAdded, dateUpdated)
Actors = Actors(nfo.Actors, dateAdded, dateUpdated),
Directors = nfo.Directors.Map(d => new Director { Name = d }).ToList(),
Writers = nfo.Writers.Map(w => new Writer { Name = w }).ToList()
};
},
None);

6
ErsatzTV.Core/Metadata/Nfo/MovieNfo.cs

@ -39,5 +39,11 @@ namespace ErsatzTV.Core.Metadata.Nfo @@ -39,5 +39,11 @@ namespace ErsatzTV.Core.Metadata.Nfo
[XmlElement("actor")]
public List<ActorNfo> Actors { get; set; }
[XmlElement("credits")]
public List<string> Writers { get; set; }
[XmlElement("director")]
public List<string> Directors { get; set; }
}
}

44
ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs

@ -254,6 +254,50 @@ namespace ErsatzTV.Core.Plex @@ -254,6 +254,50 @@ namespace ErsatzTV.Core.Plex
}
}
foreach (Director director in existingMetadata.Directors
.Filter(g => fullMetadata.Directors.All(g2 => g2.Name != g.Name))
.ToList())
{
existingMetadata.Directors.Remove(director);
if (await _movieRepository.RemoveDirector(director))
{
result.IsUpdated = true;
}
}
foreach (Director director in fullMetadata.Directors
.Filter(g => existingMetadata.Directors.All(g2 => g2.Name != g.Name))
.ToList())
{
existingMetadata.Directors.Add(director);
if (await _movieRepository.AddDirector(existingMetadata, director))
{
result.IsUpdated = true;
}
}
foreach (Writer writer in existingMetadata.Writers
.Filter(g => fullMetadata.Writers.All(g2 => g2.Name != g.Name))
.ToList())
{
existingMetadata.Writers.Remove(writer);
if (await _movieRepository.RemoveWriter(writer))
{
result.IsUpdated = true;
}
}
foreach (Writer writer in fullMetadata.Writers
.Filter(g => existingMetadata.Writers.All(g2 => g2.Name != g.Name))
.ToList())
{
existingMetadata.Writers.Add(writer);
if (await _movieRepository.AddWriter(existingMetadata, writer))
{
result.IsUpdated = true;
}
}
if (fullMetadata.SortTitle != existingMetadata.SortTitle)
{
existingMetadata.SortTitle = fullMetadata.SortTitle;

11
ErsatzTV.Infrastructure/Data/Configurations/Metadata/DirectorConfiguration.cs

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class DirectorConfiguration : IEntityTypeConfiguration<Director>
{
public void Configure(EntityTypeBuilder<Director> builder) => builder.ToTable("Director");
}
}

8
ErsatzTV.Infrastructure/Data/Configurations/Metadata/MovieMetadataConfiguration.cs

@ -29,6 +29,14 @@ namespace ErsatzTV.Infrastructure.Data.Configurations @@ -29,6 +29,14 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
builder.HasMany(mm => mm.Actors)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
builder.HasMany(mm => mm.Directors)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
builder.HasMany(mm => mm.Writers)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
}
}
}

11
ErsatzTV.Infrastructure/Data/Configurations/Metadata/Writer.cs

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class WriterConfiguration : IEntityTypeConfiguration<Writer>
{
public void Configure(EntityTypeBuilder<Writer> builder) => builder.ToTable("Writer");
}
}

13
ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs

@ -75,6 +75,19 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -75,6 +75,19 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
}
}
if (metadata is MovieMetadata movieMetadata)
{
foreach (Director director in Optional(movieMetadata.Directors).Flatten())
{
dbContext.Entry(director).State = EntityState.Added;
}
foreach (Writer writer in Optional(movieMetadata.Writers).Flatten())
{
dbContext.Entry(writer).State = EntityState.Added;
}
}
return await dbContext.SaveChangesAsync() > 0;
}

100
ErsatzTV.Infrastructure/Data/Repositories/MovieRepository.cs

@ -49,6 +49,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -49,6 +49,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.Include(m => m.MovieMetadata)
.ThenInclude(m => m.Actors)
.ThenInclude(a => a.Artwork)
.Include(i => i.MovieMetadata)
.ThenInclude(mm => mm.Directors)
.Include(i => i.MovieMetadata)
.ThenInclude(mm => mm.Writers)
.Include(m => m.MediaVersions)
.ThenInclude(mv => mv.Streams)
.OrderBy(m => m.Id)
@ -70,7 +74,13 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -70,7 +74,13 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.ThenInclude(mm => mm.Studios)
.Include(i => i.MovieMetadata)
.ThenInclude(mm => mm.Actors)
.Include(i => i.MovieMetadata)
.ThenInclude(mm => mm.Actors)
.ThenInclude(a => a.Artwork)
.Include(i => i.MovieMetadata)
.ThenInclude(mm => mm.Directors)
.Include(i => i.MovieMetadata)
.ThenInclude(mm => mm.Writers)
.Include(i => i.LibraryPath)
.ThenInclude(lp => lp.Library)
.Include(i => i.MediaVersions)
@ -105,6 +115,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -105,6 +115,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.ThenInclude(a => a.Artwork)
.Include(i => i.MovieMetadata)
.ThenInclude(mm => mm.Artwork)
.Include(i => i.MovieMetadata)
.ThenInclude(mm => mm.Directors)
.Include(i => i.MovieMetadata)
.ThenInclude(mm => mm.Writers)
.Include(i => i.MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.Include(i => i.MediaVersions)
@ -310,6 +324,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -310,6 +324,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.ThenInclude(mm => mm.Actors)
.Include(m => m.MovieMetadata)
.ThenInclude(mm => mm.Artwork)
.Include(m => m.MovieMetadata)
.ThenInclude(mm => mm.Directors)
.Include(m => m.MovieMetadata)
.ThenInclude(mm => mm.Writers)
.Filter(m => m.ItemId == movie.ItemId)
.OrderBy(m => m.ItemId)
.SingleOrDefaultAsync();
@ -399,6 +417,36 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -399,6 +417,36 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
metadata.Actors.Add(actor);
}
// directors
foreach (Director director in metadata.Directors
.Filter(d => incomingMetadata.Directors.All(d2 => d2.Name != d.Name))
.ToList())
{
metadata.Directors.Remove(director);
}
foreach (Director director in incomingMetadata.Directors
.Filter(d => metadata.Directors.All(d2 => d2.Name != d.Name))
.ToList())
{
metadata.Directors.Add(director);
}
// writers
foreach (Writer writer in metadata.Writers
.Filter(w => incomingMetadata.Writers.All(w2 => w2.Name != w.Name))
.ToList())
{
metadata.Writers.Remove(writer);
}
foreach (Writer writer in incomingMetadata.Writers
.Filter(w => metadata.Writers.All(w2 => w2.Name != w.Name))
.ToList())
{
metadata.Writers.Add(writer);
}
metadata.ReleaseDate = incomingMetadata.ReleaseDate;
// poster
@ -512,6 +560,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -512,6 +560,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.Include(m => m.MovieMetadata)
.ThenInclude(mm => mm.Actors)
.Include(m => m.MovieMetadata)
.ThenInclude(mm => mm.Directors)
.Include(m => m.MovieMetadata)
.ThenInclude(mm => mm.Writers)
.Include(m => m.MovieMetadata)
.ThenInclude(mm => mm.Artwork)
.Filter(m => m.ItemId == movie.ItemId)
.OrderBy(m => m.ItemId)
@ -602,6 +654,36 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -602,6 +654,36 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
metadata.Actors.Add(actor);
}
// directors
foreach (Director director in metadata.Directors
.Filter(d => incomingMetadata.Directors.All(d2 => d2.Name != d.Name))
.ToList())
{
metadata.Directors.Remove(director);
}
foreach (Director director in incomingMetadata.Directors
.Filter(d => metadata.Directors.All(d2 => d2.Name != d.Name))
.ToList())
{
metadata.Directors.Add(director);
}
// writers
foreach (Writer writer in metadata.Writers
.Filter(w => incomingMetadata.Writers.All(w2 => w2.Name != w.Name))
.ToList())
{
metadata.Writers.Remove(writer);
}
foreach (Writer writer in incomingMetadata.Writers
.Filter(w => metadata.Writers.All(w2 => w2.Name != w.Name))
.ToList())
{
metadata.Writers.Add(writer);
}
metadata.ReleaseDate = incomingMetadata.ReleaseDate;
// poster
@ -655,6 +737,24 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -655,6 +737,24 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
return maybeExisting;
}
public Task<bool> RemoveDirector(Director director) =>
_dbConnection.ExecuteAsync("DELETE FROM Director WHERE Id = @DirectorId", new { DirectorId = director.Id })
.Map(result => result > 0);
public Task<bool> AddDirector(MovieMetadata metadata, Director director) =>
_dbConnection.ExecuteAsync(
"INSERT INTO Director (Name, MovieMetadataId) VALUES (@Name, @MetadataId)",
new { director.Name, MetadataId = metadata.Id }).Map(result => result > 0);
public Task<bool> RemoveWriter(Writer writer) =>
_dbConnection.ExecuteAsync("DELETE FROM Writer WHERE Id = @WriterId", new { WriterId = writer.Id })
.Map(result => result > 0);
public Task<bool> AddWriter(MovieMetadata metadata, Writer writer) =>
_dbConnection.ExecuteAsync(
"INSERT INTO Writer (Name, MovieMetadataId) VALUES (@Name, @MetadataId)",
new { writer.Name, MetadataId = metadata.Id }).Map(result => result > 0);
private static async Task<Either<BaseError, MediaItemScanResult<Movie>>> AddMovie(
TvContext dbContext,
int libraryPathId,

4
ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs

@ -41,6 +41,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -41,6 +41,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.ThenInclude(mm => mm.Studios)
.Include(mi => (mi as Movie).MovieMetadata)
.ThenInclude(mm => mm.Actors)
.Include(mi => (mi as Movie).MovieMetadata)
.ThenInclude(mm => mm.Directors)
.Include(mi => (mi as Movie).MovieMetadata)
.ThenInclude(mm => mm.Writers)
.Include(mi => (mi as Movie).MediaVersions)
.ThenInclude(mm => mm.Streams)
.Include(mi => (mi as Show).ShowMetadata)

33
ErsatzTV.Infrastructure/Emby/EmbyApiClient.cs

@ -241,7 +241,9 @@ namespace ErsatzTV.Infrastructure.Emby @@ -241,7 +241,9 @@ namespace ErsatzTV.Infrastructure.Emby
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(),
Actors = Optional(item.People).Flatten().Map(r => ProjectToModel(r, dateAdded)).ToList(),
Actors = Optional(item.People).Flatten().Collect(r => ProjectToActor(r, dateAdded)).ToList(),
Directors = Optional(item.People).Flatten().Collect(r => ProjectToDirector(r)).ToList(),
Writers = Optional(item.People).Flatten().Collect(r => ProjectToWriter(r)).ToList(),
Artwork = new List<Artwork>()
};
@ -281,8 +283,13 @@ namespace ErsatzTV.Infrastructure.Emby @@ -281,8 +283,13 @@ namespace ErsatzTV.Infrastructure.Emby
return metadata;
}
private Actor ProjectToModel(EmbyPersonResponse person, DateTime dateAdded)
private Option<Actor> ProjectToActor(EmbyPersonResponse person, DateTime dateAdded)
{
if (person.Type?.ToLowerInvariant() != "actor")
{
return None;
}
var actor = new Actor { Name = person.Name, Role = person.Role };
if (!string.IsNullOrWhiteSpace(person.Id) && !string.IsNullOrWhiteSpace(person.PrimaryImageTag))
{
@ -297,6 +304,26 @@ namespace ErsatzTV.Infrastructure.Emby @@ -297,6 +304,26 @@ namespace ErsatzTV.Infrastructure.Emby
return actor;
}
private static Option<Director> ProjectToDirector(EmbyPersonResponse person)
{
if (person.Type?.ToLowerInvariant() != "director")
{
return None;
}
return new Director { Name = person.Name };
}
private static Option<Writer> ProjectToWriter(EmbyPersonResponse person)
{
if (person.Type?.ToLowerInvariant() != "writer")
{
return None;
}
return new Writer { Name = person.Name };
}
private Option<EmbyShow> ProjectToShow(EmbyLibraryItemResponse item)
{
try
@ -337,7 +364,7 @@ namespace ErsatzTV.Infrastructure.Emby @@ -337,7 +364,7 @@ namespace ErsatzTV.Infrastructure.Emby
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(),
Actors = Optional(item.People).Flatten().Map(r => ProjectToModel(r, dateAdded)).ToList(),
Actors = Optional(item.People).Flatten().Collect(r => ProjectToActor(r, dateAdded)).ToList(),
Artwork = new List<Artwork>()
};

33
ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs

@ -287,7 +287,9 @@ namespace ErsatzTV.Infrastructure.Jellyfin @@ -287,7 +287,9 @@ namespace ErsatzTV.Infrastructure.Jellyfin
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(),
Actors = Optional(item.People).Flatten().Map(r => ProjectToModel(r, dateAdded)).ToList(),
Actors = Optional(item.People).Flatten().Collect(r => ProjectToActor(r, dateAdded)).ToList(),
Directors = Optional(item.People).Flatten().Collect(r => ProjectToDirector(r)).ToList(),
Writers = Optional(item.People).Flatten().Collect(r => ProjectToWriter(r)).ToList(),
Artwork = new List<Artwork>()
};
@ -327,8 +329,13 @@ namespace ErsatzTV.Infrastructure.Jellyfin @@ -327,8 +329,13 @@ namespace ErsatzTV.Infrastructure.Jellyfin
return metadata;
}
private Actor ProjectToModel(JellyfinPersonResponse person, DateTime dateAdded)
private static Option<Actor> ProjectToActor(JellyfinPersonResponse person, DateTime dateAdded)
{
if (person.Type?.ToLowerInvariant() != "actor")
{
return None;
}
var actor = new Actor { Name = person.Name, Role = person.Role };
if (!string.IsNullOrWhiteSpace(person.Id) && !string.IsNullOrWhiteSpace(person.PrimaryImageTag))
{
@ -343,6 +350,26 @@ namespace ErsatzTV.Infrastructure.Jellyfin @@ -343,6 +350,26 @@ namespace ErsatzTV.Infrastructure.Jellyfin
return actor;
}
private static Option<Director> ProjectToDirector(JellyfinPersonResponse person)
{
if (person.Type?.ToLowerInvariant() != "director")
{
return None;
}
return new Director { Name = person.Name };
}
private static Option<Writer> ProjectToWriter(JellyfinPersonResponse person)
{
if (person.Type?.ToLowerInvariant() != "writer")
{
return None;
}
return new Writer { Name = person.Name };
}
private Option<JellyfinShow> ProjectToShow(JellyfinLibraryItemResponse item)
{
try
@ -388,7 +415,7 @@ namespace ErsatzTV.Infrastructure.Jellyfin @@ -388,7 +415,7 @@ namespace ErsatzTV.Infrastructure.Jellyfin
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(),
Actors = Optional(item.People).Flatten().Map(r => ProjectToModel(r, dateAdded)).ToList(),
Actors = Optional(item.People).Flatten().Collect(r => ProjectToActor(r, dateAdded)).ToList(),
Artwork = new List<Artwork>()
};

2775
ErsatzTV.Infrastructure/Migrations/20210527122609_Add_MovieMetadataDirectorsWriters.Designer.cs generated

File diff suppressed because it is too large Load Diff

83
ErsatzTV.Infrastructure/Migrations/20210527122609_Add_MovieMetadataDirectorsWriters.cs

@ -0,0 +1,83 @@ @@ -0,0 +1,83 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Add_MovieMetadataDirectorsWriters : 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 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");
// jellyfin
migrationBuilder.Sql("UPDATE JellyfinMovie SET Etag = NULL");
migrationBuilder.CreateTable(
"Director",
table => new
{
Id = table.Column<int>("INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>("TEXT", nullable: true),
MovieMetadataId = table.Column<int>("INTEGER", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Director", x => x.Id);
table.ForeignKey(
"FK_Director_MovieMetadata_MovieMetadataId",
x => x.MovieMetadataId,
"MovieMetadata",
"Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
"Writer",
table => new
{
Id = table.Column<int>("INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>("TEXT", nullable: true),
MovieMetadataId = table.Column<int>("INTEGER", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Writer", x => x.Id);
table.ForeignKey(
"FK_Writer_MovieMetadata_MovieMetadataId",
x => x.MovieMetadataId,
"MovieMetadata",
"Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
"IX_Director_MovieMetadataId",
"Director",
"MovieMetadataId");
migrationBuilder.CreateIndex(
"IX_Writer_MovieMetadataId",
"Writer",
"MovieMetadataId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
"Director");
migrationBuilder.DropTable(
"Writer");
}
}
}

66
ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs

@ -282,6 +282,27 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -282,6 +282,27 @@ namespace ErsatzTV.Infrastructure.Migrations
b.ToTable("ConfigElement");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.Director",
b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int?>("MovieMetadataId")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("MovieMetadataId");
b.ToTable("Director");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.EmbyConnection",
b =>
@ -1332,6 +1353,27 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -1332,6 +1353,27 @@ namespace ErsatzTV.Infrastructure.Migrations
b.ToTable("Tag");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.Writer",
b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int?>("MovieMetadataId")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("MovieMetadataId");
b.ToTable("Writer");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.EmbyLibrary",
b =>
@ -1878,6 +1920,16 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -1878,6 +1920,16 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Navigation("MediaItem");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.Director",
b =>
{
b.HasOne("ErsatzTV.Core.Domain.MovieMetadata", null)
.WithMany("Directors")
.HasForeignKey("MovieMetadataId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.EmbyConnection",
b =>
@ -2417,6 +2469,16 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -2417,6 +2469,16 @@ namespace ErsatzTV.Infrastructure.Migrations
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.Writer",
b =>
{
b.HasOne("ErsatzTV.Core.Domain.MovieMetadata", null)
.WithMany("Writers")
.HasForeignKey("MovieMetadataId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.EmbyLibrary",
b =>
@ -2859,11 +2921,15 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -2859,11 +2921,15 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Navigation("Artwork");
b.Navigation("Directors");
b.Navigation("Genres");
b.Navigation("Studios");
b.Navigation("Tags");
b.Navigation("Writers");
});
modelBuilder.Entity(

7
ErsatzTV.Infrastructure/Plex/Models/PlexDirectorResponse.cs

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
namespace ErsatzTV.Infrastructure.Plex.Models
{
public class PlexDirectorResponse
{
public string Tag { get; set; }
}
}

2
ErsatzTV.Infrastructure/Plex/Models/PlexMetadataResponse.cs

@ -20,5 +20,7 @@ namespace ErsatzTV.Infrastructure.Plex.Models @@ -20,5 +20,7 @@ namespace ErsatzTV.Infrastructure.Plex.Models
public List<PlexMediaResponse> Media { get; set; }
public List<PlexGenreResponse> Genre { get; set; }
public List<PlexRoleResponse> Role { get; set; }
public List<PlexDirectorResponse> Director { get; set; }
public List<PlexWriterResponse> Writer { get; set; }
}
}

7
ErsatzTV.Infrastructure/Plex/Models/PlexWriterResponse.mcs.cs

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
namespace ErsatzTV.Infrastructure.Plex.Models
{
public class PlexWriterResponse
{
public string Tag { get; set; }
}
}

4
ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs

@ -263,7 +263,9 @@ namespace ErsatzTV.Infrastructure.Plex @@ -263,7 +263,9 @@ namespace ErsatzTV.Infrastructure.Plex
Tags = new List<Tag>(),
Studios = new List<Studio>(),
Actors = Optional(response.Role).Flatten().Map(r => ProjectToModel(r, dateAdded, lastWriteTime))
.ToList()
.ToList(),
Directors = Optional(response.Director).Flatten().Map(d => new Director { Name = d.Tag }).ToList(),
Writers = Optional(response.Writer).Flatten().Map(w => new Writer { Name = w.Tag }).ToList()
};
if (!string.IsNullOrWhiteSpace(response.Studio))

14
ErsatzTV.Infrastructure/Search/SearchIndex.cs

@ -49,6 +49,8 @@ namespace ErsatzTV.Infrastructure.Search @@ -49,6 +49,8 @@ namespace ErsatzTV.Infrastructure.Search
private const string MoodField = "mood";
private const string ActorField = "actor";
private const string ContentRatingField = "content_rating";
private const string DirectorField = "director";
private const string WriterField = "writer";
private const string MovieType = "movie";
private const string ShowType = "show";
@ -69,7 +71,7 @@ namespace ErsatzTV.Infrastructure.Search @@ -69,7 +71,7 @@ namespace ErsatzTV.Infrastructure.Search
_initialized = false;
}
public int Version => 10;
public int Version => 11;
public Task<bool> Initialize(ILocalFileSystem localFileSystem)
{
@ -318,6 +320,16 @@ namespace ErsatzTV.Infrastructure.Search @@ -318,6 +320,16 @@ namespace ErsatzTV.Infrastructure.Search
doc.Add(new TextField(ActorField, actor.Name, Field.Store.NO));
}
foreach (Director director in metadata.Directors)
{
doc.Add(new TextField(DirectorField, director.Name, Field.Store.NO));
}
foreach (Writer writer in metadata.Writers)
{
doc.Add(new TextField(WriterField, writer.Name, Field.Store.NO));
}
_writer.UpdateDocument(new Term(IdField, movie.Id.ToString()), doc);
}
catch (Exception ex)

28
ErsatzTV/Pages/Movie.razor

@ -99,6 +99,30 @@ @@ -99,6 +99,30 @@
}
</div>
}
@if (_sortedDirectors.Any())
{
<div style="display: flex; flex-direction: row; flex-wrap: wrap">
<MudText GutterBottom="true">Directors:&nbsp;</MudText>
<MudLink Href="@($"/search?query=director%3a%22{Uri.EscapeDataString(_sortedDirectors.Head())}%22")">@_sortedDirectors.Head()</MudLink>
@foreach (string director in _sortedDirectors.Skip(1))
{
<MudText>,&nbsp;</MudText>
<MudLink Href="@($"/search?query=director%3a%22{Uri.EscapeDataString(director)}%22")">@director</MudLink>
}
</div>
}
@if (_sortedWriters.Any())
{
<div style="display: flex; flex-direction: row; flex-wrap: wrap">
<MudText GutterBottom="true">Writers:&nbsp;</MudText>
<MudLink Href="@($"/search?query=writer%3a%22{Uri.EscapeDataString(_sortedWriters.Head())}%22")">@_sortedWriters.Head()</MudLink>
@foreach (string writer in _sortedWriters.Skip(1))
{
<MudText>,&nbsp;</MudText>
<MudLink Href="@($"/search?query=writer%3a%22{Uri.EscapeDataString(writer)}%22")">@writer</MudLink>
}
</div>
}
@if (_sortedGenres.Any())
{
<div style="display: flex; flex-direction: row; flex-wrap: wrap">
@ -150,6 +174,8 @@ @@ -150,6 +174,8 @@
private MovieViewModel _movie;
private List<string> _sortedContentRatings = new();
private List<CultureInfo> _sortedLanguages = new();
private List<string> _sortedDirectors = new();
private List<string> _sortedWriters = new();
private List<string> _sortedStudios = new();
private List<string> _sortedGenres = new();
private List<string> _sortedTags = new();
@ -165,6 +191,8 @@ @@ -165,6 +191,8 @@
_sortedStudios = _movie.Studios.OrderBy(s => s).ToList();
_sortedGenres = _movie.Genres.OrderBy(g => g).ToList();
_sortedTags = _movie.Tags.OrderBy(t => t).ToList();
_sortedDirectors = _movie.Directors.OrderBy(d => d).ToList();
_sortedWriters = _movie.Writers.OrderBy(w => w).ToList();
});
private async Task AddToCollection()

Loading…
Cancel
Save