Browse Source

sync tv show thumb art (#214)

* sync thumb art from local, jellyfin, emby

* code cleanup
pull/216/head
Jason Dove 5 years ago committed by GitHub
parent
commit
466d33f808
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      ErsatzTV.Core/Emby/EmbyUrl.cs
  2. 51
      ErsatzTV.Core/Iptv/ChannelGuide.cs
  3. 10
      ErsatzTV.Core/Jellyfin/JellyfinUrl.cs
  4. 8
      ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs
  5. 35
      ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs
  6. 35
      ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs
  7. 11
      ErsatzTV.Infrastructure/Emby/EmbyApiClient.cs
  8. 1
      ErsatzTV.Infrastructure/Emby/Models/EmbyImageTagsResponse.cs
  9. 11
      ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs
  10. 1
      ErsatzTV.Infrastructure/Jellyfin/Models/JellyfinImageTagsResponse.cs
  11. 10
      ErsatzTV/Controllers/ArtworkController.cs
  12. 2
      ErsatzTV/Pages/Artist.razor
  13. 5
      ErsatzTV/Services/RunOnce/ResourceExtractorService.cs

10
ErsatzTV.Core/Emby/EmbyUrl.cs

@ -42,7 +42,7 @@ namespace ErsatzTV.Core.Emby @@ -42,7 +42,7 @@ namespace ErsatzTV.Core.Emby
.SetQueryParams(query);
}
public static Url ProxyForArtwork(string scheme, string host, string artwork)
public static Url ProxyForArtwork(string scheme, string host, string artwork, ArtworkKind artworkKind)
{
string[] split = artwork.Replace("emby://", string.Empty).Split('?');
if (split.Length != 2)
@ -53,7 +53,13 @@ namespace ErsatzTV.Core.Emby @@ -53,7 +53,13 @@ namespace ErsatzTV.Core.Emby
string pathSegment = split[0];
QueryParamCollection query = Url.ParseQueryParams(split[1]);
return Url.Parse($"{scheme}://{host}/iptv/artwork/posters/emby")
string artworkFolder = artworkKind switch
{
ArtworkKind.Thumbnail => "thumbnails",
_ => "posters"
};
return Url.Parse($"{scheme}://{host}/iptv/artwork/{artworkFolder}/emby")
.AppendPathSegment(pathSegment)
.SetQueryParams(query);
}

51
ErsatzTV.Core/Iptv/ChannelGuide.cs

@ -110,7 +110,7 @@ namespace ErsatzTV.Core.Iptv @@ -110,7 +110,7 @@ namespace ErsatzTV.Core.Iptv
string poster = Optional(metadata.Artwork).Flatten()
.Filter(a => a.ArtworkKind == ArtworkKind.Poster)
.HeadOrNone()
.Match(GetPoster, () => string.Empty);
.Match(a => GetArtworkUrl(a, ArtworkKind.Poster), () => string.Empty);
if (!string.IsNullOrWhiteSpace(poster))
{
@ -144,15 +144,24 @@ namespace ErsatzTV.Core.Iptv @@ -144,15 +144,24 @@ namespace ErsatzTV.Core.Iptv
if (maybeMetadata.IsSome)
{
ShowMetadata metadata = maybeMetadata.ValueUnsafe();
string poster = Optional(metadata.Artwork).Flatten()
string artwork = Optional(metadata.Artwork).Flatten()
.Filter(a => a.ArtworkKind == ArtworkKind.Thumbnail)
.HeadOrNone()
.Match(a => GetArtworkUrl(a, ArtworkKind.Thumbnail), () => string.Empty);
// fall back to poster
if (string.IsNullOrWhiteSpace(artwork))
{
artwork = Optional(metadata.Artwork).Flatten()
.Filter(a => a.ArtworkKind == ArtworkKind.Poster)
.HeadOrNone()
.Match(GetPoster, () => string.Empty);
.Match(a => GetArtworkUrl(a, ArtworkKind.Poster), () => string.Empty);
}
if (!string.IsNullOrWhiteSpace(poster))
if (!string.IsNullOrWhiteSpace(artwork))
{
xml.WriteStartElement("icon");
xml.WriteAttributeString("src", poster);
xml.WriteAttributeString("src", artwork);
xml.WriteEndElement(); // icon
}
}
@ -206,26 +215,38 @@ namespace ErsatzTV.Core.Iptv @@ -206,26 +215,38 @@ namespace ErsatzTV.Core.Iptv
return Encoding.UTF8.GetString(ms.ToArray());
}
private string GetPoster(Artwork artwork)
private string GetArtworkUrl(Artwork artwork, ArtworkKind artworkKind)
{
string poster = artwork.Path;
string artworkPath = artwork.Path;
if (poster.StartsWith("jellyfin://"))
int height = artworkKind switch
{
poster = JellyfinUrl.ProxyForArtwork(_scheme, _host, poster)
.SetQueryParam("fillHeight", 440);
ArtworkKind.Thumbnail => 220,
_ => 440
};
if (artworkPath.StartsWith("jellyfin://"))
{
artworkPath = JellyfinUrl.ProxyForArtwork(_scheme, _host, artworkPath, artworkKind)
.SetQueryParam("fillHeight", height);
}
else if (poster.StartsWith("emby://"))
else if (artworkPath.StartsWith("emby://"))
{
poster = EmbyUrl.ProxyForArtwork(_scheme, _host, poster)
.SetQueryParam("maxHeight", 440);
artworkPath = EmbyUrl.ProxyForArtwork(_scheme, _host, artworkPath, artworkKind)
.SetQueryParam("maxHeight", height);
}
else
{
poster = $"{_scheme}://{_host}/iptv/artwork/posters/{artwork.Path}";
string artworkFolder = artworkKind switch
{
ArtworkKind.Thumbnail => "thumbnails",
_ => "posters"
};
artworkPath = $"{_scheme}://{_host}/iptv/artwork/{artworkFolder}/{artwork.Path}";
}
return poster;
return artworkPath;
}
private static string GetTitle(PlayoutItem playoutItem)

10
ErsatzTV.Core/Jellyfin/JellyfinUrl.cs

@ -42,7 +42,7 @@ namespace ErsatzTV.Core.Jellyfin @@ -42,7 +42,7 @@ namespace ErsatzTV.Core.Jellyfin
.SetQueryParams(query);
}
public static Url ProxyForArtwork(string scheme, string host, string artwork)
public static Url ProxyForArtwork(string scheme, string host, string artwork, ArtworkKind artworkKind)
{
string[] split = artwork.Replace("jellyfin://", string.Empty).Split('?');
if (split.Length != 2)
@ -53,7 +53,13 @@ namespace ErsatzTV.Core.Jellyfin @@ -53,7 +53,13 @@ namespace ErsatzTV.Core.Jellyfin
string pathSegment = split[0];
QueryParamCollection query = Url.ParseQueryParams(split[1]);
return Url.Parse($"{scheme}://{host}/iptv/artwork/posters/jellyfin")
string artworkFolder = artworkKind switch
{
ArtworkKind.Thumbnail => "thumbnails",
_ => "posters"
};
return Url.Parse($"{scheme}://{host}/iptv/artwork/{artworkFolder}/jellyfin")
.AppendPathSegment(pathSegment)
.SetQueryParams(query);
}

8
ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs

@ -84,7 +84,8 @@ namespace ErsatzTV.Core.Metadata @@ -84,7 +84,8 @@ namespace ErsatzTV.Core.Metadata
await FindOrCreateShow(libraryPath.Id, showFolder)
.BindT(show => UpdateMetadataForShow(show, showFolder))
.BindT(show => UpdateArtworkForShow(show, showFolder, ArtworkKind.Poster))
.BindT(show => UpdateArtworkForShow(show, showFolder, ArtworkKind.FanArt));
.BindT(show => UpdateArtworkForShow(show, showFolder, ArtworkKind.FanArt))
.BindT(show => UpdateArtworkForShow(show, showFolder, ArtworkKind.Thumbnail));
await maybeShow.Match(
async result =>
@ -309,10 +310,10 @@ namespace ErsatzTV.Core.Metadata @@ -309,10 +310,10 @@ namespace ErsatzTV.Core.Metadata
{
Show show = result.Item;
await LocateArtworkForShow(showFolder, artworkKind).IfSomeAsync(
async posterFile =>
async artworkFile =>
{
ShowMetadata metadata = show.ShowMetadata.Head();
await RefreshArtwork(posterFile, metadata, artworkKind);
await RefreshArtwork(artworkFile, metadata, artworkKind);
});
return result;
@ -378,6 +379,7 @@ namespace ErsatzTV.Core.Metadata @@ -378,6 +379,7 @@ namespace ErsatzTV.Core.Metadata
{
ArtworkKind.Poster => new[] { "poster", "folder" },
ArtworkKind.FanArt => new[] { "fanart" },
ArtworkKind.Thumbnail => new[] { "thumb" },
_ => throw new ArgumentOutOfRangeException(nameof(artworkKind))
};

35
ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs

@ -210,6 +210,12 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -210,6 +210,12 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
fanArt.DateAdded = incomingFanArt.DateAdded;
fanArt.DateUpdated = incomingFanArt.DateUpdated;
}
var paths = incomingMetadata.Artwork.Map(a => a.Path).ToList();
foreach (Artwork artworkToRemove in metadata.Artwork.Filter(a => !paths.Contains(a.Path)))
{
metadata.Artwork.Remove(artworkToRemove);
}
}
await dbContext.SaveChangesAsync();
@ -280,6 +286,23 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -280,6 +286,23 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
poster.DateUpdated = incomingPoster.DateUpdated;
}
// thumbnail
Artwork incomingThumbnail =
incomingMetadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Thumbnail);
if (incomingThumbnail != null)
{
Artwork thumb = metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Thumbnail);
if (thumb == null)
{
thumb = new Artwork { ArtworkKind = ArtworkKind.Thumbnail };
metadata.Artwork.Add(thumb);
}
thumb.Path = incomingThumbnail.Path;
thumb.DateAdded = incomingThumbnail.DateAdded;
thumb.DateUpdated = incomingThumbnail.DateUpdated;
}
// fan art
Artwork incomingFanArt =
incomingMetadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.FanArt);
@ -296,6 +319,12 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -296,6 +319,12 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
fanArt.DateAdded = incomingFanArt.DateAdded;
fanArt.DateUpdated = incomingFanArt.DateUpdated;
}
var paths = incomingMetadata.Artwork.Map(a => a.Path).ToList();
foreach (Artwork artworkToRemove in metadata.Artwork.Filter(a => !paths.Contains(a.Path)))
{
metadata.Artwork.Remove(artworkToRemove);
}
}
await dbContext.SaveChangesAsync();
@ -371,6 +400,12 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -371,6 +400,12 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
thumbnail.DateUpdated = incomingThumbnail.DateUpdated;
}
var paths = incomingMetadata.Artwork.Map(a => a.Path).ToList();
foreach (Artwork artworkToRemove in metadata.Artwork.Filter(a => !paths.Contains(a.Path)))
{
metadata.Artwork.Remove(artworkToRemove);
}
// version
MediaVersion version = existing.MediaVersions.Head();
MediaVersion incomingVersion = episode.MediaVersions.Head();

35
ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs

@ -194,6 +194,23 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -194,6 +194,23 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
poster.DateUpdated = incomingPoster.DateUpdated;
}
// thumbnail
Artwork incomingThumbnail =
incomingMetadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Thumbnail);
if (incomingThumbnail != null)
{
Artwork thumb = metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Thumbnail);
if (thumb == null)
{
thumb = new Artwork { ArtworkKind = ArtworkKind.Thumbnail };
metadata.Artwork.Add(thumb);
}
thumb.Path = incomingThumbnail.Path;
thumb.DateAdded = incomingThumbnail.DateAdded;
thumb.DateUpdated = incomingThumbnail.DateUpdated;
}
// fan art
Artwork incomingFanArt =
incomingMetadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.FanArt);
@ -210,6 +227,12 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -210,6 +227,12 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
fanArt.DateAdded = incomingFanArt.DateAdded;
fanArt.DateUpdated = incomingFanArt.DateUpdated;
}
var paths = incomingMetadata.Artwork.Map(a => a.Path).ToList();
foreach (Artwork artworkToRemove in metadata.Artwork.Filter(a => !paths.Contains(a.Path)))
{
metadata.Artwork.Remove(artworkToRemove);
}
}
await dbContext.SaveChangesAsync();
@ -296,6 +319,12 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -296,6 +319,12 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
fanArt.DateAdded = incomingFanArt.DateAdded;
fanArt.DateUpdated = incomingFanArt.DateUpdated;
}
var paths = incomingMetadata.Artwork.Map(a => a.Path).ToList();
foreach (Artwork artworkToRemove in metadata.Artwork.Filter(a => !paths.Contains(a.Path)))
{
metadata.Artwork.Remove(artworkToRemove);
}
}
await dbContext.SaveChangesAsync();
@ -371,6 +400,12 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -371,6 +400,12 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
thumbnail.DateUpdated = incomingThumbnail.DateUpdated;
}
var paths = incomingMetadata.Artwork.Map(a => a.Path).ToList();
foreach (Artwork artworkToRemove in metadata.Artwork.Filter(a => !paths.Contains(a.Path)))
{
metadata.Artwork.Remove(artworkToRemove);
}
// version
MediaVersion version = existing.MediaVersions.Head();
MediaVersion incomingVersion = episode.MediaVersions.Head();

11
ErsatzTV.Infrastructure/Emby/EmbyApiClient.cs

@ -359,6 +359,17 @@ namespace ErsatzTV.Infrastructure.Emby @@ -359,6 +359,17 @@ namespace ErsatzTV.Infrastructure.Emby
metadata.Artwork.Add(poster);
}
if (!string.IsNullOrWhiteSpace(item.ImageTags.Thumb))
{
var thumb = new Artwork
{
ArtworkKind = ArtworkKind.Thumbnail,
Path = $"emby://Items/{item.Id}/Images/Thumb?tag={item.ImageTags.Thumb}",
DateAdded = dateAdded
};
metadata.Artwork.Add(thumb);
}
if (item.BackdropImageTags.Any())
{
var fanArt = new Artwork

1
ErsatzTV.Infrastructure/Emby/Models/EmbyImageTagsResponse.cs

@ -3,5 +3,6 @@ @@ -3,5 +3,6 @@
public class EmbyImageTagsResponse
{
public string Primary { get; set; }
public string Thumb { get; set; }
}
}

11
ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs

@ -410,6 +410,17 @@ namespace ErsatzTV.Infrastructure.Jellyfin @@ -410,6 +410,17 @@ namespace ErsatzTV.Infrastructure.Jellyfin
metadata.Artwork.Add(poster);
}
if (!string.IsNullOrWhiteSpace(item.ImageTags.Thumb))
{
var thumb = new Artwork
{
ArtworkKind = ArtworkKind.Thumbnail,
Path = $"jellyfin://Items/{item.Id}/Images/Thumb?tag={item.ImageTags.Thumb}",
DateAdded = dateAdded
};
metadata.Artwork.Add(thumb);
}
if (item.BackdropImageTags.Any())
{
var fanArt = new Artwork

1
ErsatzTV.Infrastructure/Jellyfin/Models/JellyfinImageTagsResponse.cs

@ -3,5 +3,6 @@ @@ -3,5 +3,6 @@
public class JellyfinImageTagsResponse
{
public string Primary { get; set; }
public string Thumb { get; set; }
}
}

10
ErsatzTV/Controllers/ArtworkController.cs

@ -56,9 +56,12 @@ namespace ErsatzTV.Controllers @@ -56,9 +56,12 @@ namespace ErsatzTV.Controllers
Right: r => new FileContentResult(r.Contents, r.MimeType));
}
[HttpGet("/iptv/artwork/posters/jellyfin/{*path}")]
[HttpGet("/artwork/posters/jellyfin/{*path}")]
public Task<IActionResult> GetJellyfinPoster(string path)
[HttpGet("/iptv/artwork/thumbnails/jellyfin/{*path}")]
[HttpGet("/artwork/thumbnails/jellyfin/{*path}")]
public Task<IActionResult> GetJellyfin(string path)
{
if (Request.QueryString.HasValue)
{
@ -70,7 +73,9 @@ namespace ErsatzTV.Controllers @@ -70,7 +73,9 @@ namespace ErsatzTV.Controllers
[HttpGet("/iptv/artwork/posters/emby/{*path}")]
[HttpGet("/artwork/posters/emby/{*path}")]
public Task<IActionResult> GetEmbyPoster(string path)
[HttpGet("/iptv/artwork/thumbnails/emby/{*path}")]
[HttpGet("/artwork/thumbnails/emby/{*path}")]
public Task<IActionResult> GetEmby(string path)
{
if (Request.QueryString.HasValue)
{
@ -99,6 +104,7 @@ namespace ErsatzTV.Controllers @@ -99,6 +104,7 @@ namespace ErsatzTV.Controllers
plexMediaSourceId,
$"photo/:/transcode?url=/{path}&height=220&width=392&minSize=1&upscale=0");
[HttpGet("/iptv/artwork/thumbnails/{fileName}")]
[HttpGet("/artwork/thumbnails/{fileName}")]
public async Task<IActionResult> GetThumbnail(string fileName)
{

2
ErsatzTV/Pages/Artist.razor

@ -26,7 +26,7 @@ @@ -26,7 +26,7 @@
{
<img class="mud-elevation-2 mr-6"
style="border-radius: 4px; flex-shrink: 0; height: 220px; width: 220px"
src="@($"/artwork/thumbnails/{_artist.Thumbnail}")" alt="artist thumnail"/>
src="@($"/artwork/thumbnails/{_artist.Thumbnail}")" alt="artist thumbnail"/>
}
<div style="display: flex; flex-direction: column; height: 100%">
<MudText Typo="Typo.h2" Class="media-item-title">@_artist.Name</MudText>

5
ErsatzTV/Services/RunOnce/ResourceExtractorService.cs

@ -23,10 +23,7 @@ namespace ErsatzTV.Services.RunOnce @@ -23,10 +23,7 @@ namespace ErsatzTV.Services.RunOnce
await ExtractResource(assembly, "Roboto-Regular.ttf", cancellationToken);
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
private async Task ExtractResource(Assembly assembly, string name, CancellationToken cancellationToken)
{

Loading…
Cancel
Save