Browse Source

add show media info button to movie and episode detail pages (#1253)

pull/1254/head
Jason Dove 2 years ago committed by GitHub
parent
commit
73c6758537
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 19
      ErsatzTV.Application/MediaItems/MediaItemInfo.cs
  3. 23
      ErsatzTV.Application/MediaItems/MediaItemInfoStream.cs
  4. 5
      ErsatzTV.Application/MediaItems/Queries/GetMediaItemInfo.cs
  5. 97
      ErsatzTV.Application/MediaItems/Queries/GetMediaItemInfoHandler.cs
  6. 32
      ErsatzTV/Pages/Movie.razor
  7. 32
      ErsatzTV/Pages/TelevisionEpisodeList.razor
  8. 62
      ErsatzTV/Shared/MediaItemInfoDialog.razor

1
CHANGELOG.md

@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Add `Season, Episode` playback order
- This is currently *only* available when a show is added directly to a schedule
- This will ignore release date and sort exclusively by season number and then by episode number
- Add `Show Media Info` button to movie and episode detail pages for troubleshooting
### Fixed
- Limit `HLS Direct` streams to realtime speed

19
ErsatzTV.Application/MediaItems/MediaItemInfo.cs

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.MediaItems;
public record MediaItemInfo(
int Id,
string Kind,
string LibraryKind,
string ServerName,
string LibraryName,
MediaItemState State,
TimeSpan Duration,
string SampleAspectRatio,
string DisplayAspectRatio,
string RFrameRate,
VideoScanKind VideoScanKind,
int Width,
int Height,
List<MediaItemInfoStream> Streams);

23
ErsatzTV.Application/MediaItems/MediaItemInfoStream.cs

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.MediaItems;
public record MediaItemInfoStream(
int Index,
MediaStreamKind Kind,
string Title,
string Codec,
string Profile,
string Language,
int? Channels,
bool Default,
bool Forced,
bool AttachedPic,
string PixelFormat,
string ColorRange,
string ColorSpace,
string ColorTransfer,
string ColorPrimaries,
int BitsPerRawSample,
string FileName,
string MimeType);

5
ErsatzTV.Application/MediaItems/Queries/GetMediaItemInfo.cs

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
using ErsatzTV.Core;
namespace ErsatzTV.Application.MediaItems;
public record GetMediaItemInfo(int Id) : IRequest<Either<BaseError, MediaItemInfo>>;

97
ErsatzTV.Application/MediaItems/Queries/GetMediaItemInfoHandler.cs

@ -0,0 +1,97 @@ @@ -0,0 +1,97 @@
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).MediaVersions)
.ThenInclude(mv => mv.Streams)
.Include(i => (i as Episode).MediaVersions)
.ThenInclude(mv => mv.Streams)
.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
};
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,
version.Streams.OrderBy(s => s.Index).Map(Project).ToList());
}
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,
mediaStream.Forced,
mediaStream.AttachedPic,
mediaStream.PixelFormat,
mediaStream.ColorRange,
mediaStream.ColorSpace,
mediaStream.ColorTransfer,
mediaStream.ColorPrimaries,
mediaStream.BitsPerRawSample,
mediaStream.FileName,
mediaStream.MimeType);
}

32
ErsatzTV/Pages/Movie.razor

@ -4,10 +4,13 @@ @@ -4,10 +4,13 @@
@using System.Globalization
@using ErsatzTV.Application.MediaCards
@using ErsatzTV.Application.MediaCollections
@using ErsatzTV.Application.MediaItems
@implements IDisposable
@inject IMediator _mediator
@inject IDialogService _dialog
@inject NavigationManager _navigationManager
@inject ILogger<Movie> _logger
@inject ISnackbar _snackbar
<MudContainer MaxWidth="MaxWidth.False" Style="padding: 0" Class="fanart-container">
<div class="fanart-tint"></div>
@ -66,13 +69,22 @@ @@ -66,13 +69,22 @@
</MudCard>
}
<div>
<MudButton Variant="Variant.Filled"
<MudButton Class="mb-6"
Variant="Variant.Filled"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="@AddToCollection">
Add To Collection
</MudButton>
</div>
<div>
<MudButton Variant="Variant.Filled"
Color="Color.Secondary"
StartIcon="@Icons.Material.Filled.Info"
OnClick="@ShowInfo">
Show Media Info
</MudButton>
</div>
</div>
</div>
@if (_movie.MediaItemState == MediaItemState.FileNotFound)
@ -253,4 +265,22 @@ @@ -253,4 +265,22 @@
}
}
private async Task ShowInfo()
{
Either<BaseError, MediaItemInfo> maybeInfo = await _mediator.Send(new GetMediaItemInfo(MovieId));
foreach (BaseError error in maybeInfo.LeftToSeq())
{
_snackbar.Add("Unexpected error loading media info");
_logger.LogError("Unexpected error loading media info: {Error}", error.Value);
}
foreach (MediaItemInfo info in maybeInfo.RightToSeq())
{
var parameters = new DialogParameters { { "MediaItemInfo", info } };
var options = new DialogOptions { CloseButton = true, CloseOnEscapeKey = true, MaxWidth = MaxWidth.Medium, FullWidth = true };
IDialogReference dialog = await _dialog.ShowAsync<MediaItemInfoDialog>(_movie.Title, parameters, options);
DialogResult _ = await dialog.Result;
}
}
}

32
ErsatzTV/Pages/TelevisionEpisodeList.razor

@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
@using ErsatzTV.Application.Television
@using ErsatzTV.Application.MediaCards
@using ErsatzTV.Application.MediaCollections
@using ErsatzTV.Application.MediaItems
@using ErsatzTV.Application.ProgramSchedules
@implements IDisposable
@inject IMediator _mediator
@ -10,7 +11,6 @@ @@ -10,7 +11,6 @@
@inject ISnackbar _snackbar
@inject IDialogService _dialog
@inject NavigationManager _navigationManager
@inject ChannelWriter<IBackgroundServiceRequest> _channel
@inject IJSRuntime _jsRuntime
<MudContainer MaxWidth="MaxWidth.False" Style="padding: 0" Class="fanart-container">
@ -109,6 +109,14 @@ @@ -109,6 +109,14 @@
Add To Collection
</MudButton>
</div>
<div class="mt-6">
<MudButton Variant="Variant.Filled"
Color="Color.Secondary"
StartIcon="@Icons.Material.Filled.Info"
OnClick="@(_ => ShowInfo(episode))">
Show Media Info
</MudButton>
</div>
</div>
</MudCardContent>
</div>
@ -259,4 +267,26 @@ @@ -259,4 +267,26 @@
}
}
private async Task ShowInfo(TelevisionEpisodeCardViewModel episode)
{
Either<BaseError, MediaItemInfo> maybeInfo = await _mediator.Send(new GetMediaItemInfo(episode.EpisodeId));
foreach (BaseError error in maybeInfo.LeftToSeq())
{
_snackbar.Add("Unexpected error loading media info");
_logger.LogError("Unexpected error loading media info: {Error}", error.Value);
}
foreach (MediaItemInfo info in maybeInfo.RightToSeq())
{
var parameters = new DialogParameters { { "MediaItemInfo", info } };
var options = new DialogOptions { CloseButton = true, CloseOnEscapeKey = true, MaxWidth = MaxWidth.Medium, FullWidth = true };
IDialogReference dialog = await _dialog.ShowAsync<MediaItemInfoDialog>(
$"{episode.ShowTitle} - s{episode.Season:00}e{episode.Episode:00} - {episode.Title}",
parameters,
options);
DialogResult _ = await dialog.Result;
}
}
}

62
ErsatzTV/Shared/MediaItemInfoDialog.razor

@ -0,0 +1,62 @@ @@ -0,0 +1,62 @@
@using ErsatzTV.Application.MediaItems
@using System.Text.Json
@using System.Text.Json.Serialization
@inject IJSRuntime JsRuntime
<div>
<MudDialog>
<DialogContent>
<div class="overflow-y-scroll" style="max-height: 500px">
<pre>
<code @ref="_infoView">@_info</code>
</pre>
</div>
</DialogContent>
<DialogActions>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="(() => CopyToClipboard(_infoView))">
Copy
</MudButton>
<MudButton Color="Color.Primary" OnClick="Close">Close</MudButton>
</DialogActions>
</MudDialog>
</div>
@code {
[CascadingParameter]
MudDialogInstance MudDialog { get; set; }
[Parameter]
public MediaItemInfo MediaItemInfo { get; set; }
private string _info;
private ElementReference _infoView;
protected override Task OnParametersSetAsync()
{
try
{
_info = JsonSerializer.Serialize(
MediaItemInfo,
new JsonSerializerOptions
{
Converters = { new JsonStringEnumConverter() },
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
WriteIndented = true
});
}
catch (Exception ex)
{
_info = ex.ToString();
}
return Task.CompletedTask;
}
private async Task CopyToClipboard(ElementReference view)
{
await JsRuntime.InvokeVoidAsync("clipboardCopy.copyText", view);
}
private void Close() => MudDialog.Close(DialogResult.Ok(true));
}
Loading…
Cancel
Save