Browse Source

fix jellyfin artwork (#198)

pull/200/head
Jason Dove 5 years ago committed by GitHub
parent
commit
1d35efa429
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      ErsatzTV.Application/MediaCards/Mapper.cs
  2. 12
      ErsatzTV.Application/Movies/Mapper.cs
  3. 12
      ErsatzTV.Application/Television/Mapper.cs
  4. 121
      ErsatzTV.Core.Tests/Jellyfin/JellyfinUrlTests.cs
  5. 1
      ErsatzTV.Core/ErsatzTV.Core.csproj
  6. 2
      ErsatzTV.Core/Jellyfin/JellyfinMovieLibraryScanner.cs
  7. 4
      ErsatzTV.Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs
  8. 31
      ErsatzTV.Core/Jellyfin/JellyfinUrl.cs
  9. 16
      ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs
  10. 2509
      ErsatzTV.Infrastructure/Migrations/20210522010528_Update_JellyfinArtworkPath.Designer.cs
  11. 16
      ErsatzTV.Infrastructure/Migrations/20210522010528_Update_JellyfinArtworkPath.cs

20
ErsatzTV.Application/MediaCards/Mapper.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Jellyfin;
using LanguageExt; using LanguageExt;
using static LanguageExt.Prelude; using static LanguageExt.Prelude;
@ -101,10 +102,8 @@ namespace ErsatzTV.Application.MediaCards
if (maybeJellyfin.IsSome && artwork.StartsWith("jellyfin://")) if (maybeJellyfin.IsSome && artwork.StartsWith("jellyfin://"))
{ {
string address = maybeJellyfin.Map(ms => ms.Connections.HeadOrNone().Map(c => c.Address)) artwork = JellyfinUrl.ForArtwork(maybeJellyfin, artwork)
.Flatten() .SetQueryParam("fillHeight", 440);
.IfNone("jellyfin://");
artwork = artwork.Replace("jellyfin://", address) + "&fillheight=440";
} }
return new ActorCardViewModel(actor.Id, actor.Name, actor.Role, artwork); return new ActorCardViewModel(actor.Id, actor.Name, actor.Role, artwork);
@ -125,10 +124,8 @@ namespace ErsatzTV.Application.MediaCards
if (maybeJellyfin.IsSome && poster.StartsWith("jellyfin://")) if (maybeJellyfin.IsSome && poster.StartsWith("jellyfin://"))
{ {
string address = maybeJellyfin.Map(ms => ms.Connections.HeadOrNone().Map(c => c.Address)) poster = JellyfinUrl.ForArtwork(maybeJellyfin, poster)
.Flatten() .SetQueryParam("fillHeight", 440);
.IfNone("jellyfin://");
poster = poster.Replace("jellyfin://", address) + "&fillHeight=440";
} }
return poster; return poster;
@ -141,11 +138,8 @@ namespace ErsatzTV.Application.MediaCards
if (maybeJellyfin.IsSome && thumb.StartsWith("jellyfin://")) if (maybeJellyfin.IsSome && thumb.StartsWith("jellyfin://"))
{ {
string address = maybeJellyfin.Map(ms => ms.Connections.HeadOrNone().Map(c => c.Address)) thumb = JellyfinUrl.ForArtwork(maybeJellyfin, thumb)
.Flatten() .SetQueryParam("fillHeight", 220);
.IfNone("jellyfin://");
thumb = thumb.Replace("jellyfin://", address) +
"&fillHeight=220"; // TODO: this height is optimized for episode
} }
return thumb; return thumb;

12
ErsatzTV.Application/Movies/Mapper.cs

@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Jellyfin;
using Flurl;
using LanguageExt; using LanguageExt;
using static LanguageExt.Prelude; using static LanguageExt.Prelude;
@ -56,15 +58,13 @@ namespace ErsatzTV.Application.Movies
if (maybeJellyfin.IsSome && artwork.StartsWith("jellyfin://")) if (maybeJellyfin.IsSome && artwork.StartsWith("jellyfin://"))
{ {
string address = maybeJellyfin.Map(ms => ms.Connections.HeadOrNone().Map(c => c.Address)) Url url = JellyfinUrl.ForArtwork(maybeJellyfin, artwork);
.Flatten()
.IfNone("jellyfin://");
artwork = artwork.Replace("jellyfin://", address);
if (artworkKind is ArtworkKind.Poster or ArtworkKind.Thumbnail) if (artworkKind is ArtworkKind.Poster or ArtworkKind.Thumbnail)
{ {
artwork += "&fillHeight=440"; url.SetQueryParam("fillHeight", 440);
} }
artwork = url;
} }
return artwork; return artwork;

12
ErsatzTV.Application/Television/Mapper.cs

@ -4,6 +4,8 @@ using System.Globalization;
using System.Linq; using System.Linq;
using ErsatzTV.Application.MediaCards; using ErsatzTV.Application.MediaCards;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Jellyfin;
using Flurl;
using LanguageExt; using LanguageExt;
using static LanguageExt.Prelude; using static LanguageExt.Prelude;
@ -74,15 +76,13 @@ namespace ErsatzTV.Application.Television
if (maybeJellyfin.IsSome && artwork.StartsWith("jellyfin://")) if (maybeJellyfin.IsSome && artwork.StartsWith("jellyfin://"))
{ {
string address = maybeJellyfin.Map(ms => ms.Connections.HeadOrNone().Map(c => c.Address)) Url url = JellyfinUrl.ForArtwork(maybeJellyfin, artwork);
.Flatten()
.IfNone("jellyfin://");
artwork = artwork.Replace("jellyfin://", address);
if (artworkKind == ArtworkKind.Poster) if (artworkKind == ArtworkKind.Poster)
{ {
artwork += "&fillHeight=440"; url.SetQueryParam("fillHeight", 440);
} }
artwork = url;
} }
return artwork; return artwork;

121
ErsatzTV.Core.Tests/Jellyfin/JellyfinUrlTests.cs

@ -0,0 +1,121 @@
using System.Collections.Generic;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Jellyfin;
using FluentAssertions;
using Flurl;
using NUnit.Framework;
using static LanguageExt.Prelude;
namespace ErsatzTV.Core.Tests.Jellyfin
{
public class JellyfinUrlTests
{
[Test]
public void Should_Work_Without_Trailing_Slash()
{
var artwork = "jellyfin://Items/2/Images/3?tag=4";
var address = "https://some.jellyfin.server";
var mediaSource = new JellyfinMediaSource
{
Connections = new List<JellyfinConnection>
{
new() { Address = address }
}
};
Url url = JellyfinUrl.ForArtwork(Some(mediaSource), artwork);
url.ToString().Should().Be("https://some.jellyfin.server/Items/2/Images/3?tag=4");
}
[Test]
public void Should_Work_With_Trailing_Slash()
{
var artwork = "jellyfin://Items/2/Images/3?tag=4";
var address = "https://some.jellyfin.server/";
var mediaSource = new JellyfinMediaSource
{
Connections = new List<JellyfinConnection>
{
new() { Address = address }
}
};
Url url = JellyfinUrl.ForArtwork(Some(mediaSource), artwork);
url.ToString().Should().Be("https://some.jellyfin.server/Items/2/Images/3?tag=4");
}
[Test]
public void Should_Work_With_Port_Without_Trailing_Slash()
{
var artwork = "jellyfin://Items/2/Images/3?tag=4";
var address = "https://some.jellyfin.server:1000";
var mediaSource = new JellyfinMediaSource
{
Connections = new List<JellyfinConnection>
{
new() { Address = address }
}
};
Url url = JellyfinUrl.ForArtwork(Some(mediaSource), artwork);
url.ToString().Should().Be("https://some.jellyfin.server:1000/Items/2/Images/3?tag=4");
}
[Test]
public void Should_Work_With_Port_With_Trailing_Slash()
{
var artwork = "jellyfin://Items/2/Images/3?tag=4";
var address = "https://some.jellyfin.server:1000/";
var mediaSource = new JellyfinMediaSource
{
Connections = new List<JellyfinConnection>
{
new() { Address = address }
}
};
Url url = JellyfinUrl.ForArtwork(Some(mediaSource), artwork);
url.ToString().Should().Be("https://some.jellyfin.server:1000/Items/2/Images/3?tag=4");
}
[Test]
public void Should_Work_With_Path_Prefix_Without_Trailing_Slash()
{
var artwork = "jellyfin://Items/2/Images/3?tag=4";
var address = "https://some.jellyfin.server/jellyfin";
var mediaSource = new JellyfinMediaSource
{
Connections = new List<JellyfinConnection>
{
new() { Address = address }
}
};
Url url = JellyfinUrl.ForArtwork(Some(mediaSource), artwork);
url.ToString().Should().Be("https://some.jellyfin.server/jellyfin/Items/2/Images/3?tag=4");
}
[Test]
public void Should_Work_With_Path_Prefix_With_Trailing_Slash()
{
var artwork = "jellyfin://Items/2/Images/3?tag=4";
var address = "https://some.jellyfin.server/jellyfin/";
var mediaSource = new JellyfinMediaSource
{
Connections = new List<JellyfinConnection>
{
new() { Address = address }
}
};
Url url = JellyfinUrl.ForArtwork(Some(mediaSource), artwork);
url.ToString().Should().Be("https://some.jellyfin.server/jellyfin/Items/2/Images/3?tag=4");
}
}
}

1
ErsatzTV.Core/ErsatzTV.Core.csproj

@ -10,6 +10,7 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Flurl" Version="3.0.2" />
<PackageReference Include="LanguageExt.Core" Version="3.4.15" /> <PackageReference Include="LanguageExt.Core" Version="3.4.15" />
<PackageReference Include="MediatR" Version="9.0.0" /> <PackageReference Include="MediatR" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />

2
ErsatzTV.Core/Jellyfin/JellyfinMovieLibraryScanner.cs

@ -86,7 +86,7 @@ namespace ErsatzTV.Core.Jellyfin
if (!_localFileSystem.FileExists(localPath)) if (!_localFileSystem.FileExists(localPath))
{ {
_logger.LogWarning($"Skipping jellyfin movie that does not exist at {localPath}"); _logger.LogWarning("Skipping jellyfin movie that does not exist at {Path}", localPath);
} }
else else
{ {

4
ErsatzTV.Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs

@ -348,7 +348,7 @@ namespace ErsatzTV.Core.Jellyfin
"UPDATE: Etag has changed for show {Show} season {Season} episode {Episode}", "UPDATE: Etag has changed for show {Show} season {Season} episode {Episode}",
showName, showName,
seasonName, seasonName,
"EPISODE"); incoming.EpisodeNumber);
updateStatistics = true; updateStatistics = true;
incoming.SeasonId = season.Id; incoming.SeasonId = season.Id;
@ -377,7 +377,7 @@ namespace ErsatzTV.Core.Jellyfin
"INSERT: Item id is new for show {Show} season {Season} episode {Episode}", "INSERT: Item id is new for show {Show} season {Season} episode {Episode}",
showName, showName,
seasonName, seasonName,
"EPISODE"); incoming.EpisodeNumber);
await _televisionRepository.AddEpisode(incoming); await _televisionRepository.AddEpisode(incoming);
} }

31
ErsatzTV.Core/Jellyfin/JellyfinUrl.cs

@ -0,0 +1,31 @@
using ErsatzTV.Core.Domain;
using Flurl;
using LanguageExt;
namespace ErsatzTV.Core.Jellyfin
{
public static class JellyfinUrl
{
public static Url ForArtwork(Option<JellyfinMediaSource> maybeJellyfin, string artwork)
{
string address = maybeJellyfin.Map(ms => ms.Connections.HeadOrNone().Map(c => c.Address))
.Flatten()
.IfNone("jellyfin://");
string[] split = artwork.Replace("jellyfin://", string.Empty).Split('?');
if (split.Length != 2)
{
return artwork;
}
string pathSegment = split[0];
QueryParamCollection query = Url.ParseQueryParams(split[1]);
Url x = Url.Parse(address)
.AppendPathSegment(pathSegment)
.SetQueryParams(query);
return x;
}
}
}

16
ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs

@ -297,7 +297,7 @@ namespace ErsatzTV.Infrastructure.Jellyfin
var poster = new Artwork var poster = new Artwork
{ {
ArtworkKind = ArtworkKind.Poster, ArtworkKind = ArtworkKind.Poster,
Path = $"jellyfin:///Items/{item.Id}/Images/Primary?tag={item.ImageTags.Primary}", Path = $"jellyfin://Items/{item.Id}/Images/Primary?tag={item.ImageTags.Primary}",
DateAdded = dateAdded DateAdded = dateAdded
}; };
metadata.Artwork.Add(poster); metadata.Artwork.Add(poster);
@ -308,7 +308,7 @@ namespace ErsatzTV.Infrastructure.Jellyfin
var fanArt = new Artwork var fanArt = new Artwork
{ {
ArtworkKind = ArtworkKind.FanArt, ArtworkKind = ArtworkKind.FanArt,
Path = $"jellyfin:///Items/{item.Id}/Images/Backdrop?tag={item.BackdropImageTags.Head()}", Path = $"jellyfin://Items/{item.Id}/Images/Backdrop?tag={item.BackdropImageTags.Head()}",
DateAdded = dateAdded DateAdded = dateAdded
}; };
metadata.Artwork.Add(fanArt); metadata.Artwork.Add(fanArt);
@ -324,7 +324,7 @@ namespace ErsatzTV.Infrastructure.Jellyfin
{ {
actor.Artwork = new Artwork actor.Artwork = new Artwork
{ {
Path = $"jellyfin:///Items/{person.Id}/Images/Primary?tag={person.PrimaryImageTag}", Path = $"jellyfin://Items/{person.Id}/Images/Primary?tag={person.PrimaryImageTag}",
ArtworkKind = ArtworkKind.Thumbnail, ArtworkKind = ArtworkKind.Thumbnail,
DateAdded = dateAdded DateAdded = dateAdded
}; };
@ -396,7 +396,7 @@ namespace ErsatzTV.Infrastructure.Jellyfin
var poster = new Artwork var poster = new Artwork
{ {
ArtworkKind = ArtworkKind.Poster, ArtworkKind = ArtworkKind.Poster,
Path = $"jellyfin:///Items/{item.Id}/Images/Primary?tag={item.ImageTags.Primary}", Path = $"jellyfin://Items/{item.Id}/Images/Primary?tag={item.ImageTags.Primary}",
DateAdded = dateAdded DateAdded = dateAdded
}; };
metadata.Artwork.Add(poster); metadata.Artwork.Add(poster);
@ -407,7 +407,7 @@ namespace ErsatzTV.Infrastructure.Jellyfin
var fanArt = new Artwork var fanArt = new Artwork
{ {
ArtworkKind = ArtworkKind.FanArt, ArtworkKind = ArtworkKind.FanArt,
Path = $"jellyfin:///Items/{item.Id}/Images/Backdrop?tag={item.BackdropImageTags.Head()}", Path = $"jellyfin://Items/{item.Id}/Images/Backdrop?tag={item.BackdropImageTags.Head()}",
DateAdded = dateAdded DateAdded = dateAdded
}; };
metadata.Artwork.Add(fanArt); metadata.Artwork.Add(fanArt);
@ -442,7 +442,7 @@ namespace ErsatzTV.Infrastructure.Jellyfin
var poster = new Artwork var poster = new Artwork
{ {
ArtworkKind = ArtworkKind.Poster, ArtworkKind = ArtworkKind.Poster,
Path = $"jellyfin:///Items/{item.Id}/Images/Primary?tag={item.ImageTags.Primary}", Path = $"jellyfin://Items/{item.Id}/Images/Primary?tag={item.ImageTags.Primary}",
DateAdded = dateAdded DateAdded = dateAdded
}; };
metadata.Artwork.Add(poster); metadata.Artwork.Add(poster);
@ -453,7 +453,7 @@ namespace ErsatzTV.Infrastructure.Jellyfin
var fanArt = new Artwork var fanArt = new Artwork
{ {
ArtworkKind = ArtworkKind.FanArt, ArtworkKind = ArtworkKind.FanArt,
Path = $"jellyfin:///Items/{item.Id}/Images/Backdrop?tag={item.BackdropImageTags.Head()}", Path = $"jellyfin://Items/{item.Id}/Images/Backdrop?tag={item.BackdropImageTags.Head()}",
DateAdded = dateAdded DateAdded = dateAdded
}; };
metadata.Artwork.Add(fanArt); metadata.Artwork.Add(fanArt);
@ -557,7 +557,7 @@ namespace ErsatzTV.Infrastructure.Jellyfin
var thumbnail = new Artwork var thumbnail = new Artwork
{ {
ArtworkKind = ArtworkKind.Thumbnail, ArtworkKind = ArtworkKind.Thumbnail,
Path = $"jellyfin:///Items/{item.Id}/Images/Primary?tag={item.ImageTags.Primary}", Path = $"jellyfin://Items/{item.Id}/Images/Primary?tag={item.ImageTags.Primary}",
DateAdded = dateAdded DateAdded = dateAdded
}; };
metadata.Artwork.Add(thumbnail); metadata.Artwork.Add(thumbnail);

2509
ErsatzTV.Infrastructure/Migrations/20210522010528_Update_JellyfinArtworkPath.Designer.cs generated

File diff suppressed because it is too large Load Diff

16
ErsatzTV.Infrastructure/Migrations/20210522010528_Update_JellyfinArtworkPath.cs

@ -0,0 +1,16 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Update_JellyfinArtworkPath : Migration
{
protected override void Up(MigrationBuilder migrationBuilder) =>
migrationBuilder.Sql(
@"UPDATE Artwork SET Path = REPLACE(Path, 'jellyfin:///Items', 'jellyfin://Items')
WHERE Path LIKE 'jellyfin:///Items%'");
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}
Loading…
Cancel
Save