add jellyfin media source (#185)

* wip

* start to add jellyfin tables to db

* code cleanup

* finish adding jellyfin media source

* sync jellyfin libraries

* display list of jellyfin libraries

* toggle jellyfin library sync

* edit jellyfin path replacements

* noop jellyfin scanners

* get jellyfin admin user id on startup

* implement jellyfin disconnect

* add jellyfin libraries to list; start to query jellyfin library items

* code cleanup

* start to project jellyfin movies

* save new jellyfin movies to db

* basic jellyfin movie update

* load jellyfin actor artwork

* load jellyfin movie poster and fan art

* more jellyfin artwork fixes, sync audio streams

* jellyfin playback sort of works

* skip jellyfin movies that are inaccessible

* use ffprobe for jellyfin movie statistics

* code cleanup

* store jellyfin operating system

* more jellyfin movie updates

* update jellyfin movie poster and fan art

* add jellyfin tv types

* sync jellyfin shows

* sync jellyfin seasons

* sync jellyfin episodes

* remove missing jellyfin television items

* delete empty jellyfin seasons and shows

* fix jellyfin updates

* fix indexing jellyfin movie and show languages
This commit is contained in:
Jason Dove
2021-05-15 13:14:17 -05:00
committed by GitHub
parent 27e0a70d93
commit 4d86250630
132 changed files with 17318 additions and 106 deletions
@@ -445,6 +445,51 @@ namespace ErsatzTV.Infrastructure.Migrations
b.ToTable("Genre");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.JellyfinConnection",
b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Address")
.HasColumnType("TEXT");
b.Property<int>("JellyfinMediaSourceId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("JellyfinMediaSourceId");
b.ToTable("JellyfinConnection");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.JellyfinPathReplacement",
b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("JellyfinMediaSourceId")
.HasColumnType("INTEGER");
b.Property<string>("JellyfinPath")
.HasColumnType("TEXT");
b.Property<string>("LocalPath")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("JellyfinMediaSourceId");
b.ToTable("JellyfinPathReplacement");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.Library",
b =>
@@ -1209,6 +1254,21 @@ namespace ErsatzTV.Infrastructure.Migrations
b.ToTable("Tag");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.JellyfinLibrary",
b =>
{
b.HasBaseType("ErsatzTV.Core.Domain.Library");
b.Property<string>("ItemId")
.HasColumnType("TEXT");
b.Property<bool>("ShouldSyncItems")
.HasColumnType("INTEGER");
b.ToTable("JellyfinLibrary");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.LocalLibrary",
b =>
@@ -1323,6 +1383,21 @@ namespace ErsatzTV.Infrastructure.Migrations
b.ToTable("Show");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.JellyfinMediaSource",
b =>
{
b.HasBaseType("ErsatzTV.Core.Domain.MediaSource");
b.Property<string>("OperatingSystem")
.HasColumnType("TEXT");
b.Property<string>("ServerName")
.HasColumnType("TEXT");
b.ToTable("JellyfinMediaSource");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.LocalMediaSource",
b =>
@@ -1401,6 +1476,21 @@ namespace ErsatzTV.Infrastructure.Migrations
b.ToTable("ProgramScheduleOneItem");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.JellyfinEpisode",
b =>
{
b.HasBaseType("ErsatzTV.Core.Domain.Episode");
b.Property<string>("Etag")
.HasColumnType("TEXT");
b.Property<string>("ItemId")
.HasColumnType("TEXT");
b.ToTable("JellyfinEpisode");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.PlexEpisode",
b =>
@@ -1413,6 +1503,21 @@ namespace ErsatzTV.Infrastructure.Migrations
b.ToTable("PlexEpisode");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.JellyfinMovie",
b =>
{
b.HasBaseType("ErsatzTV.Core.Domain.Movie");
b.Property<string>("Etag")
.HasColumnType("TEXT");
b.Property<string>("ItemId")
.HasColumnType("TEXT");
b.ToTable("JellyfinMovie");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.PlexMovie",
b =>
@@ -1425,6 +1530,21 @@ namespace ErsatzTV.Infrastructure.Migrations
b.ToTable("PlexMovie");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.JellyfinSeason",
b =>
{
b.HasBaseType("ErsatzTV.Core.Domain.Season");
b.Property<string>("Etag")
.HasColumnType("TEXT");
b.Property<string>("ItemId")
.HasColumnType("TEXT");
b.ToTable("JellyfinSeason");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.PlexSeason",
b =>
@@ -1437,6 +1557,21 @@ namespace ErsatzTV.Infrastructure.Migrations
b.ToTable("PlexSeason");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.JellyfinShow",
b =>
{
b.HasBaseType("ErsatzTV.Core.Domain.Show");
b.Property<string>("Etag")
.HasColumnType("TEXT");
b.Property<string>("ItemId")
.HasColumnType("TEXT");
b.ToTable("JellyfinShow");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.PlexShow",
b =>
@@ -1634,6 +1769,32 @@ namespace ErsatzTV.Infrastructure.Migrations
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.JellyfinConnection",
b =>
{
b.HasOne("ErsatzTV.Core.Domain.JellyfinMediaSource", "JellyfinMediaSource")
.WithMany("Connections")
.HasForeignKey("JellyfinMediaSourceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("JellyfinMediaSource");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.JellyfinPathReplacement",
b =>
{
b.HasOne("ErsatzTV.Core.Domain.JellyfinMediaSource", "JellyfinMediaSource")
.WithMany("PathReplacements")
.HasForeignKey("JellyfinMediaSourceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("JellyfinMediaSource");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.Library",
b =>
@@ -2049,6 +2210,17 @@ namespace ErsatzTV.Infrastructure.Migrations
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.JellyfinLibrary",
b =>
{
b.HasOne("ErsatzTV.Core.Domain.Library", null)
.WithOne()
.HasForeignKey("ErsatzTV.Core.Domain.JellyfinLibrary", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.LocalLibrary",
b =>
@@ -2172,6 +2344,17 @@ namespace ErsatzTV.Infrastructure.Migrations
.IsRequired();
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.JellyfinMediaSource",
b =>
{
b.HasOne("ErsatzTV.Core.Domain.MediaSource", null)
.WithOne()
.HasForeignKey("ErsatzTV.Core.Domain.JellyfinMediaSource", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.LocalMediaSource",
b =>
@@ -2238,6 +2421,17 @@ namespace ErsatzTV.Infrastructure.Migrations
.IsRequired();
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.JellyfinEpisode",
b =>
{
b.HasOne("ErsatzTV.Core.Domain.Episode", null)
.WithOne()
.HasForeignKey("ErsatzTV.Core.Domain.JellyfinEpisode", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.PlexEpisode",
b =>
@@ -2249,6 +2443,17 @@ namespace ErsatzTV.Infrastructure.Migrations
.IsRequired();
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.JellyfinMovie",
b =>
{
b.HasOne("ErsatzTV.Core.Domain.Movie", null)
.WithOne()
.HasForeignKey("ErsatzTV.Core.Domain.JellyfinMovie", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.PlexMovie",
b =>
@@ -2260,6 +2465,17 @@ namespace ErsatzTV.Infrastructure.Migrations
.IsRequired();
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.JellyfinSeason",
b =>
{
b.HasOne("ErsatzTV.Core.Domain.Season", null)
.WithOne()
.HasForeignKey("ErsatzTV.Core.Domain.JellyfinSeason", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.PlexSeason",
b =>
@@ -2271,6 +2487,17 @@ namespace ErsatzTV.Infrastructure.Migrations
.IsRequired();
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.JellyfinShow",
b =>
{
b.HasOne("ErsatzTV.Core.Domain.Show", null)
.WithOne()
.HasForeignKey("ErsatzTV.Core.Domain.JellyfinShow", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.PlexShow",
b =>
@@ -2476,6 +2703,15 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Navigation("ShowMetadata");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.JellyfinMediaSource",
b =>
{
b.Navigation("Connections");
b.Navigation("PathReplacements");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.PlexMediaSource",
b =>