mirror of https://github.com/ErsatzTV/ErsatzTV.git
26 changed files with 1155 additions and 59 deletions
@ -1,7 +1,9 @@
@@ -1,7 +1,9 @@
|
||||
namespace ErsatzTV.Application.MediaItems |
||||
{ |
||||
public record AggregateMediaItemViewModel( |
||||
int MediaItemId, |
||||
string Title, |
||||
string Subtitle, |
||||
string SortTitle) : IMediaCard; |
||||
string SortTitle, |
||||
bool HasPoster); |
||||
} |
||||
|
||||
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
namespace ErsatzTV.Application.MediaItems.Commands |
||||
{ |
||||
public record RefreshMediaItemPoster : RefreshMediaItem |
||||
{ |
||||
public RefreshMediaItemPoster(int mediaItemId) : base(mediaItemId) |
||||
{ |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
using ErsatzTV.Application.Images; |
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.MediaItems.Queries |
||||
{ |
||||
public record GetPosterContents(int MediaItemId) : IRequest<Either<BaseError, ImageViewModel>>; |
||||
} |
||||
@ -0,0 +1,71 @@
@@ -0,0 +1,71 @@
|
||||
using System; |
||||
using System.IO; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Application.Images; |
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
using Microsoft.Extensions.Caching.Memory; |
||||
using Winista.Mime; |
||||
using static LanguageExt.Prelude; |
||||
|
||||
namespace ErsatzTV.Application.MediaItems.Queries |
||||
{ |
||||
public class GetPosterContentsHandler : IRequestHandler<GetPosterContents, Either<BaseError, ImageViewModel>> |
||||
{ |
||||
private static readonly MimeTypes MimeTypes = new(); |
||||
|
||||
private readonly IMediaItemRepository _mediaItemRepository; |
||||
private readonly IMemoryCache _memoryCache; |
||||
|
||||
public GetPosterContentsHandler(IMediaItemRepository mediaItemRepository, IMemoryCache memoryCache) |
||||
{ |
||||
_mediaItemRepository = mediaItemRepository; |
||||
_memoryCache = memoryCache; |
||||
} |
||||
|
||||
public Task<Either<BaseError, ImageViewModel>> Handle( |
||||
GetPosterContents request, |
||||
CancellationToken cancellationToken) => |
||||
Validate(request) |
||||
.Map(v => v.ToEither<MediaItem>()) |
||||
.BindT(GetPoster); |
||||
|
||||
private async Task<Either<BaseError, ImageViewModel>> GetPoster(MediaItem mediaItem) |
||||
{ |
||||
try |
||||
{ |
||||
return await _memoryCache.GetOrCreateAsync( |
||||
mediaItem.PosterPath, |
||||
async entry => |
||||
{ |
||||
entry.SlidingExpiration = TimeSpan.FromHours(1); |
||||
|
||||
byte[] contents = await File.ReadAllBytesAsync(mediaItem.PosterPath); |
||||
MimeType mimeType = MimeTypes.GetMimeType(contents); |
||||
return new ImageViewModel(contents, mimeType.Name); |
||||
}); |
||||
} |
||||
catch (Exception ex) |
||||
{ |
||||
return BaseError.New(ex.Message); |
||||
} |
||||
} |
||||
|
||||
private Task<Validation<BaseError, MediaItem>> Validate(GetPosterContents request) => |
||||
MediaItemMustExist(request).BindT(PosterPathMustExist); |
||||
|
||||
private async Task<Validation<BaseError, MediaItem>> MediaItemMustExist(GetPosterContents request) => |
||||
(await _mediaItemRepository.Get(request.MediaItemId)) |
||||
.ToValidation<BaseError>($"MediaItem {request.MediaItemId} does not exist."); |
||||
|
||||
private static Validation<BaseError, MediaItem> PosterPathMustExist(MediaItem mediaItem) => |
||||
Optional(mediaItem.PosterPath) |
||||
.Filter(p => !string.IsNullOrWhiteSpace(p) && File.Exists(p)) |
||||
.Map(_ => mediaItem) |
||||
.ToValidation<BaseError>($"MediaItem {mediaItem.Id} does not have a poster"); |
||||
} |
||||
} |
||||
@ -1,4 +1,4 @@
@@ -1,4 +1,4 @@
|
||||
namespace ErsatzTV.Core.AggregateModels |
||||
{ |
||||
public record MediaItemSummary(string Title, string SortTitle, string Subtitle); |
||||
public record MediaItemSummary(string Title, string SortTitle, string Subtitle, string PosterPath, int MediaItemId); |
||||
} |
||||
|
||||
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core.Domain; |
||||
|
||||
namespace ErsatzTV.Core.Interfaces.Metadata |
||||
{ |
||||
public interface ILocalPosterProvider |
||||
{ |
||||
Task RefreshPoster(MediaItem mediaItem); |
||||
} |
||||
} |
||||
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Interfaces.Metadata; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using LanguageExt; |
||||
using static LanguageExt.Prelude; |
||||
|
||||
namespace ErsatzTV.Core.Metadata |
||||
{ |
||||
public class LocalPosterProvider : ILocalPosterProvider |
||||
{ |
||||
private readonly IMediaItemRepository _mediaItemRepository; |
||||
|
||||
public LocalPosterProvider(IMediaItemRepository mediaItemRepository) => |
||||
_mediaItemRepository = mediaItemRepository; |
||||
|
||||
public Task RefreshPoster(MediaItem mediaItem) |
||||
{ |
||||
Option<string> maybePoster = mediaItem.Metadata.MediaType switch |
||||
{ |
||||
MediaType.Movie => RefreshMoviePoster(mediaItem), |
||||
MediaType.TvShow => RefreshTelevisionPoster(mediaItem), |
||||
_ => None |
||||
}; |
||||
|
||||
return maybePoster.Match( |
||||
path => |
||||
{ |
||||
mediaItem.PosterPath = path; |
||||
return _mediaItemRepository.Update(mediaItem); |
||||
}, |
||||
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) => None; |
||||
} |
||||
} |
||||
@ -0,0 +1,857 @@
@@ -0,0 +1,857 @@
|
||||
// <auto-generated />
|
||||
using System; |
||||
using ErsatzTV.Infrastructure.Data; |
||||
using Microsoft.EntityFrameworkCore; |
||||
using Microsoft.EntityFrameworkCore.Infrastructure; |
||||
using Microsoft.EntityFrameworkCore.Migrations; |
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; |
||||
|
||||
namespace ErsatzTV.Infrastructure.Migrations |
||||
{ |
||||
[DbContext(typeof(TvContext))] |
||||
[Migration("20210213191309_MediaItemPosterPath")] |
||||
partial class MediaItemPosterPath |
||||
{ |
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder) |
||||
{ |
||||
#pragma warning disable 612, 618
|
||||
modelBuilder |
||||
.HasAnnotation("ProductVersion", "5.0.3"); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => |
||||
{ |
||||
b.Property<int>("Id") |
||||
.ValueGeneratedOnAdd() |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("FFmpegProfileId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<string>("Logo") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.Property<string>("Name") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.Property<int>("Number") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("StreamingMode") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<Guid>("UniqueId") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.HasKey("Id"); |
||||
|
||||
b.HasIndex("FFmpegProfileId"); |
||||
|
||||
b.HasIndex("Number") |
||||
.IsUnique(); |
||||
|
||||
b.ToTable("Channels"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ConfigElement", b => |
||||
{ |
||||
b.Property<int>("Id") |
||||
.ValueGeneratedOnAdd() |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<string>("Key") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.Property<string>("Value") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.HasKey("Id"); |
||||
|
||||
b.HasIndex("Key") |
||||
.IsUnique(); |
||||
|
||||
b.ToTable("ConfigElements"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.FFmpegProfile", b => |
||||
{ |
||||
b.Property<int>("Id") |
||||
.ValueGeneratedOnAdd() |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("AudioBitrate") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("AudioBufferSize") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("AudioChannels") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<string>("AudioCodec") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.Property<int>("AudioSampleRate") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("AudioVolume") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<string>("Name") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.Property<bool>("NormalizeAudio") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<bool>("NormalizeAudioCodec") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<bool>("NormalizeResolution") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<bool>("NormalizeVideoCodec") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("ResolutionId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("ThreadCount") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<bool>("Transcode") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("VideoBitrate") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("VideoBufferSize") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<string>("VideoCodec") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.HasKey("Id"); |
||||
|
||||
b.HasIndex("ResolutionId"); |
||||
|
||||
b.ToTable("FFmpegProfiles"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.MediaCollection", b => |
||||
{ |
||||
b.Property<int>("Id") |
||||
.ValueGeneratedOnAdd() |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<string>("Name") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.HasKey("Id"); |
||||
|
||||
b.HasIndex("Name") |
||||
.IsUnique(); |
||||
|
||||
b.ToTable("MediaCollections"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => |
||||
{ |
||||
b.Property<int>("Id") |
||||
.ValueGeneratedOnAdd() |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<DateTime?>("LastWriteTime") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.Property<int>("MediaSourceId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<string>("Path") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.Property<string>("PosterPath") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.HasKey("Id"); |
||||
|
||||
b.HasIndex("MediaSourceId"); |
||||
|
||||
b.ToTable("MediaItems"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.MediaSource", b => |
||||
{ |
||||
b.Property<int>("Id") |
||||
.ValueGeneratedOnAdd() |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<string>("Name") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.Property<int>("SourceType") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.HasKey("Id"); |
||||
|
||||
b.HasIndex("Name") |
||||
.IsUnique(); |
||||
|
||||
b.ToTable("MediaSources"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => |
||||
{ |
||||
b.Property<int>("Id") |
||||
.ValueGeneratedOnAdd() |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("ChannelId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("ProgramScheduleId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("ProgramSchedulePlayoutType") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.HasKey("Id"); |
||||
|
||||
b.HasIndex("ChannelId"); |
||||
|
||||
b.HasIndex("ProgramScheduleId"); |
||||
|
||||
b.ToTable("Playouts"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutItem", b => |
||||
{ |
||||
b.Property<int>("Id") |
||||
.ValueGeneratedOnAdd() |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<DateTimeOffset>("Finish") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.Property<int>("MediaItemId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("PlayoutId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<DateTimeOffset>("Start") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.HasKey("Id"); |
||||
|
||||
b.HasIndex("MediaItemId"); |
||||
|
||||
b.HasIndex("PlayoutId"); |
||||
|
||||
b.ToTable("PlayoutItems"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", b => |
||||
{ |
||||
b.Property<int>("PlayoutId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("ProgramScheduleId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("MediaCollectionId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.HasKey("PlayoutId", "ProgramScheduleId", "MediaCollectionId"); |
||||
|
||||
b.HasIndex("MediaCollectionId"); |
||||
|
||||
b.HasIndex("ProgramScheduleId"); |
||||
|
||||
b.ToTable("PlayoutProgramScheduleItemAnchors"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSourceConnection", b => |
||||
{ |
||||
b.Property<int>("Id") |
||||
.ValueGeneratedOnAdd() |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<bool>("IsActive") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int?>("PlexMediaSourceId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<string>("Uri") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.HasKey("Id"); |
||||
|
||||
b.HasIndex("PlexMediaSourceId"); |
||||
|
||||
b.ToTable("PlexMediaSourceConnections"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSourceLibrary", b => |
||||
{ |
||||
b.Property<int>("Id") |
||||
.ValueGeneratedOnAdd() |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<string>("Key") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.Property<int>("MediaType") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<string>("Name") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.Property<int?>("PlexMediaSourceId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.HasKey("Id"); |
||||
|
||||
b.HasIndex("PlexMediaSourceId"); |
||||
|
||||
b.ToTable("PlexMediaSourceLibraries"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b => |
||||
{ |
||||
b.Property<int>("Id") |
||||
.ValueGeneratedOnAdd() |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("MediaCollectionPlaybackOrder") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<string>("Name") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.HasKey("Id"); |
||||
|
||||
b.HasIndex("Name") |
||||
.IsUnique(); |
||||
|
||||
b.ToTable("ProgramSchedules"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b => |
||||
{ |
||||
b.Property<int>("Id") |
||||
.ValueGeneratedOnAdd() |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("Index") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("MediaCollectionId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("ProgramScheduleId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<TimeSpan?>("StartTime") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.HasKey("Id"); |
||||
|
||||
b.HasIndex("MediaCollectionId"); |
||||
|
||||
b.HasIndex("ProgramScheduleId"); |
||||
|
||||
b.ToTable("ProgramScheduleItems"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Resolution", b => |
||||
{ |
||||
b.Property<int>("Id") |
||||
.ValueGeneratedOnAdd() |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("Height") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<string>("Name") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.Property<int>("Width") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.HasKey("Id"); |
||||
|
||||
b.ToTable("Resolutions"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("MediaItemSimpleMediaCollection", b => |
||||
{ |
||||
b.Property<int>("ItemsId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<int>("SimpleMediaCollectionsId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.HasKey("ItemsId", "SimpleMediaCollectionsId"); |
||||
|
||||
b.HasIndex("SimpleMediaCollectionsId"); |
||||
|
||||
b.ToTable("MediaItemSimpleMediaCollection"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.SimpleMediaCollection", b => |
||||
{ |
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaCollection"); |
||||
|
||||
b.ToTable("SimpleMediaCollections"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionMediaCollection", b => |
||||
{ |
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaCollection"); |
||||
|
||||
b.Property<int?>("SeasonNumber") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<string>("ShowTitle") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.HasIndex("ShowTitle", "SeasonNumber") |
||||
.IsUnique(); |
||||
|
||||
b.ToTable("TelevisionMediaCollections"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b => |
||||
{ |
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaSource"); |
||||
|
||||
b.Property<string>("Folder") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.Property<int>("MediaType") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.ToTable("LocalMediaSources"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => |
||||
{ |
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaSource"); |
||||
|
||||
b.Property<string>("ClientIdentifier") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.Property<string>("ProductVersion") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.ToTable("PlexMediaSources"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", b => |
||||
{ |
||||
b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); |
||||
|
||||
b.Property<bool>("OfflineTail") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.Property<TimeSpan>("PlayoutDuration") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b.ToTable("ProgramScheduleDurationItems"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", b => |
||||
{ |
||||
b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); |
||||
|
||||
b.ToTable("ProgramScheduleFloodItems"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", b => |
||||
{ |
||||
b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); |
||||
|
||||
b.Property<int>("Count") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b.ToTable("ProgramScheduleMultipleItems"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemOne", b => |
||||
{ |
||||
b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); |
||||
|
||||
b.ToTable("ProgramScheduleOneItems"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => |
||||
{ |
||||
b.HasOne("ErsatzTV.Core.Domain.FFmpegProfile", "FFmpegProfile") |
||||
.WithMany() |
||||
.HasForeignKey("FFmpegProfileId") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
|
||||
b.Navigation("FFmpegProfile"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.FFmpegProfile", b => |
||||
{ |
||||
b.HasOne("ErsatzTV.Core.Domain.Resolution", "Resolution") |
||||
.WithMany() |
||||
.HasForeignKey("ResolutionId") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
|
||||
b.Navigation("Resolution"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => |
||||
{ |
||||
b.HasOne("ErsatzTV.Core.Domain.MediaSource", "Source") |
||||
.WithMany() |
||||
.HasForeignKey("MediaSourceId") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
|
||||
b.OwnsOne("ErsatzTV.Core.Domain.MediaMetadata", "Metadata", b1 => |
||||
{ |
||||
b1.Property<int>("MediaItemId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b1.Property<DateTime?>("Aired") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b1.Property<string>("AudioCodec") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b1.Property<string>("ContentRating") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b1.Property<string>("Description") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b1.Property<string>("DisplayAspectRatio") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b1.Property<TimeSpan>("Duration") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b1.Property<int?>("EpisodeNumber") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b1.Property<int>("Height") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b1.Property<int>("MediaType") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b1.Property<string>("SampleAspectRatio") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b1.Property<int?>("SeasonNumber") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b1.Property<string>("SortTitle") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b1.Property<string>("Subtitle") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b1.Property<string>("Title") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b1.Property<string>("VideoCodec") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b1.Property<int>("VideoScanType") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b1.Property<int>("Width") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b1.HasKey("MediaItemId"); |
||||
|
||||
b1.ToTable("MediaItems"); |
||||
|
||||
b1.WithOwner() |
||||
.HasForeignKey("MediaItemId"); |
||||
}); |
||||
|
||||
b.Navigation("Metadata"); |
||||
|
||||
b.Navigation("Source"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => |
||||
{ |
||||
b.HasOne("ErsatzTV.Core.Domain.Channel", "Channel") |
||||
.WithMany("Playouts") |
||||
.HasForeignKey("ChannelId") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
|
||||
b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") |
||||
.WithMany("Playouts") |
||||
.HasForeignKey("ProgramScheduleId") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
|
||||
b.OwnsOne("ErsatzTV.Core.Domain.PlayoutAnchor", "Anchor", b1 => |
||||
{ |
||||
b1.Property<int>("PlayoutId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b1.Property<int>("NextScheduleItemId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b1.Property<DateTimeOffset>("NextStart") |
||||
.HasColumnType("TEXT"); |
||||
|
||||
b1.HasKey("PlayoutId"); |
||||
|
||||
b1.HasIndex("NextScheduleItemId"); |
||||
|
||||
b1.ToTable("Playouts"); |
||||
|
||||
b1.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", "NextScheduleItem") |
||||
.WithMany() |
||||
.HasForeignKey("NextScheduleItemId") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
|
||||
b1.WithOwner() |
||||
.HasForeignKey("PlayoutId"); |
||||
|
||||
b1.Navigation("NextScheduleItem"); |
||||
}); |
||||
|
||||
b.Navigation("Anchor"); |
||||
|
||||
b.Navigation("Channel"); |
||||
|
||||
b.Navigation("ProgramSchedule"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutItem", b => |
||||
{ |
||||
b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") |
||||
.WithMany() |
||||
.HasForeignKey("MediaItemId") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
|
||||
b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout") |
||||
.WithMany("Items") |
||||
.HasForeignKey("PlayoutId") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
|
||||
b.Navigation("MediaItem"); |
||||
|
||||
b.Navigation("Playout"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", b => |
||||
{ |
||||
b.HasOne("ErsatzTV.Core.Domain.MediaCollection", "MediaCollection") |
||||
.WithMany() |
||||
.HasForeignKey("MediaCollectionId") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
|
||||
b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout") |
||||
.WithMany("ProgramScheduleAnchors") |
||||
.HasForeignKey("PlayoutId") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
|
||||
b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") |
||||
.WithMany() |
||||
.HasForeignKey("ProgramScheduleId") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
|
||||
b.OwnsOne("ErsatzTV.Core.Domain.MediaCollectionEnumeratorState", "EnumeratorState", b1 => |
||||
{ |
||||
b1.Property<int>("PlayoutProgramScheduleAnchorPlayoutId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b1.Property<int>("PlayoutProgramScheduleAnchorProgramScheduleId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b1.Property<int>("PlayoutProgramScheduleAnchorMediaCollectionId") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b1.Property<int>("Index") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b1.Property<int>("Seed") |
||||
.HasColumnType("INTEGER"); |
||||
|
||||
b1.HasKey("PlayoutProgramScheduleAnchorPlayoutId", "PlayoutProgramScheduleAnchorProgramScheduleId", "PlayoutProgramScheduleAnchorMediaCollectionId"); |
||||
|
||||
b1.ToTable("PlayoutProgramScheduleItemAnchors"); |
||||
|
||||
b1.WithOwner() |
||||
.HasForeignKey("PlayoutProgramScheduleAnchorPlayoutId", "PlayoutProgramScheduleAnchorProgramScheduleId", "PlayoutProgramScheduleAnchorMediaCollectionId"); |
||||
}); |
||||
|
||||
b.Navigation("EnumeratorState"); |
||||
|
||||
b.Navigation("MediaCollection"); |
||||
|
||||
b.Navigation("Playout"); |
||||
|
||||
b.Navigation("ProgramSchedule"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSourceConnection", b => |
||||
{ |
||||
b.HasOne("ErsatzTV.Core.Domain.PlexMediaSource", null) |
||||
.WithMany("Connections") |
||||
.HasForeignKey("PlexMediaSourceId"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSourceLibrary", b => |
||||
{ |
||||
b.HasOne("ErsatzTV.Core.Domain.PlexMediaSource", null) |
||||
.WithMany("Libraries") |
||||
.HasForeignKey("PlexMediaSourceId"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b => |
||||
{ |
||||
b.HasOne("ErsatzTV.Core.Domain.MediaCollection", "MediaCollection") |
||||
.WithMany() |
||||
.HasForeignKey("MediaCollectionId") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
|
||||
b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") |
||||
.WithMany("Items") |
||||
.HasForeignKey("ProgramScheduleId") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
|
||||
b.Navigation("MediaCollection"); |
||||
|
||||
b.Navigation("ProgramSchedule"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("MediaItemSimpleMediaCollection", b => |
||||
{ |
||||
b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) |
||||
.WithMany() |
||||
.HasForeignKey("ItemsId") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
|
||||
b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) |
||||
.WithMany() |
||||
.HasForeignKey("SimpleMediaCollectionsId") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.SimpleMediaCollection", b => |
||||
{ |
||||
b.HasOne("ErsatzTV.Core.Domain.MediaCollection", null) |
||||
.WithOne() |
||||
.HasForeignKey("ErsatzTV.Core.Domain.SimpleMediaCollection", "Id") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionMediaCollection", b => |
||||
{ |
||||
b.HasOne("ErsatzTV.Core.Domain.MediaCollection", null) |
||||
.WithOne() |
||||
.HasForeignKey("ErsatzTV.Core.Domain.TelevisionMediaCollection", "Id") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b => |
||||
{ |
||||
b.HasOne("ErsatzTV.Core.Domain.MediaSource", null) |
||||
.WithOne() |
||||
.HasForeignKey("ErsatzTV.Core.Domain.LocalMediaSource", "Id") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => |
||||
{ |
||||
b.HasOne("ErsatzTV.Core.Domain.MediaSource", null) |
||||
.WithOne() |
||||
.HasForeignKey("ErsatzTV.Core.Domain.PlexMediaSource", "Id") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", b => |
||||
{ |
||||
b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) |
||||
.WithOne() |
||||
.HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", "Id") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", b => |
||||
{ |
||||
b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) |
||||
.WithOne() |
||||
.HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", "Id") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", b => |
||||
{ |
||||
b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) |
||||
.WithOne() |
||||
.HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", "Id") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemOne", b => |
||||
{ |
||||
b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) |
||||
.WithOne() |
||||
.HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemOne", "Id") |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => |
||||
{ |
||||
b.Navigation("Playouts"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => |
||||
{ |
||||
b.Navigation("Items"); |
||||
|
||||
b.Navigation("ProgramScheduleAnchors"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b => |
||||
{ |
||||
b.Navigation("Items"); |
||||
|
||||
b.Navigation("Playouts"); |
||||
}); |
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => |
||||
{ |
||||
b.Navigation("Connections"); |
||||
|
||||
b.Navigation("Libraries"); |
||||
}); |
||||
#pragma warning restore 612, 618
|
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
||||
namespace ErsatzTV.Infrastructure.Migrations |
||||
{ |
||||
public partial class MediaItemPosterPath : Migration |
||||
{ |
||||
protected override void Up(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.DropTable( |
||||
"GenericIntegerIds"); |
||||
|
||||
migrationBuilder.DropTable( |
||||
"MediaCollectionSummaries"); |
||||
|
||||
migrationBuilder.AddColumn<string>( |
||||
"PosterPath", |
||||
"MediaItems", |
||||
"TEXT", |
||||
nullable: true); |
||||
} |
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.DropColumn( |
||||
"PosterPath", |
||||
"MediaItems"); |
||||
|
||||
migrationBuilder.CreateTable( |
||||
"GenericIntegerIds", |
||||
table => new |
||||
{ |
||||
Id = table.Column<int>("INTEGER", nullable: false) |
||||
}, |
||||
constraints: table => { }); |
||||
|
||||
migrationBuilder.CreateTable( |
||||
"MediaCollectionSummaries", |
||||
table => new |
||||
{ |
||||
Id = table.Column<int>("INTEGER", nullable: false), |
||||
IsSimple = table.Column<bool>("INTEGER", nullable: false), |
||||
ItemCount = table.Column<int>("INTEGER", nullable: false), |
||||
Name = table.Column<string>("TEXT", nullable: true) |
||||
}, |
||||
constraints: table => { }); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Application.Images; |
||||
using ErsatzTV.Application.MediaItems.Queries; |
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
using Microsoft.AspNetCore.Mvc; |
||||
|
||||
namespace ErsatzTV.Controllers |
||||
{ |
||||
[ApiController] |
||||
[ApiExplorerSettings(IgnoreApi = true)] |
||||
public class PostersController : ControllerBase |
||||
{ |
||||
private readonly IMediator _mediator; |
||||
|
||||
public PostersController(IMediator mediator) => _mediator = mediator; |
||||
|
||||
[HttpGet("/posters/{mediaItemId}")] |
||||
public async Task<IActionResult> ForMediaItem(int mediaItemId) |
||||
{ |
||||
Either<BaseError, ImageViewModel> imageContents = await _mediator.Send(new GetPosterContents(mediaItemId)); |
||||
return imageContents.Match<IActionResult>( |
||||
Left: _ => new NotFoundResult(), |
||||
Right: r => new FileContentResult(r.Contents, r.MimeType)); |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue