mirror of https://github.com/ErsatzTV/ErsatzTV.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
160 lines
5.9 KiB
160 lines
5.9 KiB
using ErsatzTV.Core; |
|
using ErsatzTV.Core.Domain; |
|
using ErsatzTV.Core.Extensions; |
|
using ErsatzTV.Infrastructure.Data; |
|
using ErsatzTV.Infrastructure.Extensions; |
|
using Microsoft.EntityFrameworkCore; |
|
|
|
namespace ErsatzTV.Application.MediaItems; |
|
|
|
public class GetMediaItemInfoHandler : IRequestHandler<GetMediaItemInfo, Either<BaseError, MediaItemInfo>> |
|
{ |
|
private readonly IDbContextFactory<TvContext> _dbContextFactory; |
|
|
|
public GetMediaItemInfoHandler(IDbContextFactory<TvContext> dbContextFactory) => |
|
_dbContextFactory = dbContextFactory; |
|
|
|
public async Task<Either<BaseError, MediaItemInfo>> Handle( |
|
GetMediaItemInfo request, |
|
CancellationToken cancellationToken) |
|
{ |
|
try |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); |
|
|
|
Option<MediaItemInfo> mediaItem = await dbContext.MediaItems |
|
.AsNoTracking() |
|
.Include(i => i.LibraryPath) |
|
.ThenInclude(lp => lp.Library) |
|
.ThenInclude(l => l.MediaSource) |
|
// TODO: support all media types here |
|
.Include(i => (i as Movie).MovieMetadata) |
|
.ThenInclude(mv => mv.Subtitles) |
|
.Include(i => (i as Movie).MediaVersions) |
|
.ThenInclude(mv => mv.Chapters) |
|
.Include(i => (i as Movie).MediaVersions) |
|
.ThenInclude(mv => mv.Streams) |
|
.Include(i => (i as Episode).MediaVersions) |
|
.ThenInclude(mv => mv.Chapters) |
|
.Include(i => (i as Episode).MediaVersions) |
|
.ThenInclude(mv => mv.Streams) |
|
.Include(i => (i as Episode).EpisodeMetadata) |
|
.ThenInclude(mv => mv.Subtitles) |
|
.SelectOneAsync(i => i.Id, i => i.Id == request.Id) |
|
.MapT(Project); |
|
|
|
return mediaItem.ToEither(BaseError.New("Unable to locate media item")); |
|
} |
|
catch (Exception ex) |
|
{ |
|
return BaseError.New(ex.ToString()); |
|
} |
|
} |
|
|
|
private static MediaItemInfo Project(MediaItem mediaItem) |
|
{ |
|
MediaVersion version = mediaItem.GetHeadVersion(); |
|
|
|
string serverName = mediaItem.LibraryPath.Library.MediaSource switch |
|
{ |
|
PlexMediaSource plexMediaSource => plexMediaSource.ServerName, |
|
EmbyMediaSource embyMediaSource => embyMediaSource.ServerName, |
|
JellyfinMediaSource jellyfinMediaSource => jellyfinMediaSource.ServerName, |
|
_ => null |
|
}; |
|
|
|
var allStreams = version.Streams.OrderBy(s => s.Index).Map(Project).ToList(); |
|
|
|
List<Subtitle> subtitles = mediaItem switch |
|
{ |
|
Movie m => m.MovieMetadata.Map(mm => mm.Subtitles).Flatten().ToList(), |
|
Episode e => e.EpisodeMetadata.Map(mm => mm.Subtitles).Flatten().ToList(), |
|
_ => [] |
|
}; |
|
|
|
// include external subtitles from local libraries |
|
if (mediaItem.LibraryPath.Library is LocalLibrary) |
|
{ |
|
allStreams.AddRange(subtitles.Filter(s => s.SubtitleKind is SubtitleKind.Sidecar).Map(ProjectToStream)); |
|
} |
|
|
|
// set extracted status and file name on each subtitle stream |
|
foreach (MediaItemInfoStream subtitleStream in allStreams.Filter(s => s.Kind is MediaStreamKind.Subtitle)) |
|
{ |
|
foreach (Subtitle subtitle in subtitles.Filter(s => s.StreamIndex == subtitleStream.Index).HeadOrNone()) |
|
{ |
|
if (!subtitle.IsImage) |
|
{ |
|
subtitleStream.IsExtracted = subtitle.IsExtracted; |
|
} |
|
|
|
subtitleStream.FileName = string.IsNullOrWhiteSpace(subtitle.Path) ? null : subtitle.Path; |
|
} |
|
} |
|
|
|
var allChapters = (version.Chapters ?? []).OrderBy(c => c.StartTime).Map(Project).ToList(); |
|
|
|
return new MediaItemInfo( |
|
mediaItem.Id, |
|
mediaItem.GetType().Name, |
|
mediaItem.LibraryPath.Library.GetType().Name, |
|
serverName, |
|
mediaItem.LibraryPath.Library.Name, |
|
mediaItem.State, |
|
version.Duration, |
|
version.SampleAspectRatio, |
|
version.DisplayAspectRatio, |
|
version.RFrameRate, |
|
version.VideoScanKind, |
|
version.Width, |
|
version.Height, |
|
allStreams, |
|
allChapters); |
|
} |
|
|
|
private static MediaItemInfoStream Project(MediaStream mediaStream) => |
|
new( |
|
mediaStream.Index, |
|
mediaStream.MediaStreamKind, |
|
mediaStream.Title, |
|
mediaStream.Codec, |
|
mediaStream.Profile, |
|
mediaStream.Language, |
|
mediaStream.Channels > 0 ? mediaStream.Channels : null, |
|
mediaStream.Default ? true : null, |
|
mediaStream.Forced ? true : null, |
|
mediaStream.AttachedPic ? true : null, |
|
mediaStream.PixelFormat, |
|
mediaStream.ColorRange, |
|
mediaStream.ColorSpace, |
|
mediaStream.ColorTransfer, |
|
mediaStream.ColorPrimaries, |
|
mediaStream.BitsPerRawSample > 0 ? mediaStream.BitsPerRawSample : null, |
|
mediaStream.MimeType) { FileName = mediaStream.FileName }; |
|
|
|
private static MediaItemInfoStream ProjectToStream(Subtitle subtitle) => |
|
new( |
|
null, |
|
MediaStreamKind.ExternalSubtitle, |
|
subtitle.Title, |
|
subtitle.Codec, |
|
null, |
|
subtitle.Language, |
|
null, |
|
subtitle.Default ? true : null, |
|
subtitle.Forced ? true : null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null) |
|
{ |
|
FileName = string.IsNullOrWhiteSpace(subtitle.Path) ? null : Path.GetFileName(subtitle.Path) |
|
}; |
|
|
|
private static MediaItemInfoChapter Project(MediaChapter chapter) => |
|
new(chapter.Title, chapter.StartTime, chapter.EndTime); |
|
}
|
|
|