rework media layout (#22)
* replace media items tables with card grids * style cleanup * add basic paging * sort and page in the db * optimize sql for movies * support movie posters * resize movie posters and store in cache with channel logos * fix bug preventing folders with more than 50 chars as local media sources * support tv posters
This commit is contained in:
@@ -16,6 +16,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
public class LocalMediaScanner : ILocalMediaScanner
|
||||
{
|
||||
private readonly ILocalMetadataProvider _localMetadataProvider;
|
||||
private readonly ILocalPosterProvider _localPosterProvider;
|
||||
private readonly ILocalStatisticsProvider _localStatisticsProvider;
|
||||
private readonly ILogger<LocalMediaScanner> _logger;
|
||||
private readonly IMediaItemRepository _mediaItemRepository;
|
||||
@@ -28,6 +29,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
IPlayoutRepository playoutRepository,
|
||||
ILocalStatisticsProvider localStatisticsProvider,
|
||||
ILocalMetadataProvider localMetadataProvider,
|
||||
ILocalPosterProvider localPosterProvider,
|
||||
ISmartCollectionBuilder smartCollectionBuilder,
|
||||
IPlayoutBuilder playoutBuilder,
|
||||
ILogger<LocalMediaScanner> logger)
|
||||
@@ -36,6 +38,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
_playoutRepository = playoutRepository;
|
||||
_localStatisticsProvider = localStatisticsProvider;
|
||||
_localMetadataProvider = localMetadataProvider;
|
||||
_localPosterProvider = localPosterProvider;
|
||||
_smartCollectionBuilder = smartCollectionBuilder;
|
||||
_playoutBuilder = playoutBuilder;
|
||||
_logger = logger;
|
||||
@@ -135,6 +138,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
{
|
||||
await _localStatisticsProvider.RefreshStatistics(ffprobePath, mediaItem);
|
||||
await _localMetadataProvider.RefreshMetadata(mediaItem);
|
||||
await _localPosterProvider.RefreshPoster(mediaItem);
|
||||
await _smartCollectionBuilder.RefreshSmartCollections(mediaItem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,10 @@ namespace ErsatzTV.Core.Metadata
|
||||
mediaItem.Metadata.MediaType = metadata.MediaType;
|
||||
mediaItem.Metadata.Title = metadata.Title;
|
||||
mediaItem.Metadata.Subtitle = metadata.Subtitle;
|
||||
mediaItem.Metadata.SortTitle =
|
||||
(metadata.Title ?? string.Empty).ToLowerInvariant().StartsWith("the ")
|
||||
? metadata.Title?.Substring(4)
|
||||
: metadata.Title;
|
||||
mediaItem.Metadata.Description = metadata.Description;
|
||||
mediaItem.Metadata.EpisodeNumber = metadata.EpisodeNumber;
|
||||
mediaItem.Metadata.SeasonNumber = metadata.SeasonNumber;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Images;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static LanguageExt.Prelude;
|
||||
|
||||
namespace ErsatzTV.Core.Metadata
|
||||
{
|
||||
public class LocalPosterProvider : ILocalPosterProvider
|
||||
{
|
||||
private readonly IImageCache _imageCache;
|
||||
private readonly ILogger<LocalPosterProvider> _logger;
|
||||
private readonly IMediaItemRepository _mediaItemRepository;
|
||||
|
||||
public LocalPosterProvider(
|
||||
IMediaItemRepository mediaItemRepository,
|
||||
IImageCache imageCache,
|
||||
ILogger<LocalPosterProvider> logger)
|
||||
{
|
||||
_mediaItemRepository = mediaItemRepository;
|
||||
_imageCache = imageCache;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task RefreshPoster(MediaItem mediaItem)
|
||||
{
|
||||
Option<string> maybePosterPath = mediaItem.Metadata.MediaType switch
|
||||
{
|
||||
MediaType.Movie => RefreshMoviePoster(mediaItem),
|
||||
MediaType.TvShow => RefreshTelevisionPoster(mediaItem),
|
||||
_ => None
|
||||
};
|
||||
|
||||
await maybePosterPath.Match(
|
||||
path => SavePosterToDisk(mediaItem, path),
|
||||
Task.CompletedTask);
|
||||
}
|
||||
|
||||
private static Option<string> RefreshMoviePoster(MediaItem mediaItem)
|
||||
{
|
||||
string folder = Path.GetDirectoryName(mediaItem.Path);
|
||||
if (folder != null)
|
||||
{
|
||||
string[] possiblePaths =
|
||||
{ "poster.jpg", Path.GetFileNameWithoutExtension(mediaItem.Path) + "-poster.jpg" };
|
||||
Option<string> maybePoster =
|
||||
possiblePaths.Map(p => Path.Combine(folder, p)).FirstOrDefault(File.Exists);
|
||||
return maybePoster;
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
private Option<string> RefreshTelevisionPoster(MediaItem mediaItem)
|
||||
{
|
||||
string folder = Directory.GetParent(Path.GetDirectoryName(mediaItem.Path) ?? string.Empty)?.FullName;
|
||||
if (folder != null)
|
||||
{
|
||||
string[] possiblePaths = { "poster.jpg" };
|
||||
Option<string> maybePoster =
|
||||
possiblePaths.Map(p => Path.Combine(folder, p)).FirstOrDefault(File.Exists);
|
||||
return maybePoster;
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
private async Task SavePosterToDisk(MediaItem mediaItem, string posterPath)
|
||||
{
|
||||
byte[] originalBytes = await File.ReadAllBytesAsync(posterPath);
|
||||
Either<BaseError, string> maybeHash = await _imageCache.ResizeAndSaveImage(originalBytes, 220, null);
|
||||
await maybeHash.Match(
|
||||
hash =>
|
||||
{
|
||||
mediaItem.Poster = hash;
|
||||
return _mediaItemRepository.Update(mediaItem);
|
||||
},
|
||||
error =>
|
||||
{
|
||||
_logger.LogWarning("Unable to save poster to disk from {Path}: {Error}", posterPath, error.Value);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user