enable plex for movies (#72)
* re-enable plex, temp force secure connections * add plex fanart * synchronize genre from plex * fix plex library sync * improve stream error handling * synchronize plex artwork * use switch instead of button * prioritize local connections for insecure plex sources * sign out of plex * better plex sign in/out * code cleanup * fix plex movie aspect ratio and scan type
This commit is contained in:
@@ -18,5 +18,12 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
string key,
|
||||
[Query] [AliasAs("X-Plex-Token")]
|
||||
string token);
|
||||
|
||||
[Get("/library/metadata/{key}")]
|
||||
public Task<PlexMediaContainerResponse<PlexMediaContainerMetadataContent<PlexMetadataResponse>>>
|
||||
GetMetadata(
|
||||
string key,
|
||||
[Query] [AliasAs("X-Plex-Token")]
|
||||
string token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
|
||||
[Get("/resources")]
|
||||
Task<List<PlexResource>> GetResources(
|
||||
[Query] [AliasAs("includeHttps")]
|
||||
int includeHttps,
|
||||
[Header("X-Plex-Client-Identifier")]
|
||||
string clientIdentifier,
|
||||
[Header("X-Plex-Token")]
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
{
|
||||
public class PlexGenreResponse
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Filter { get; set; }
|
||||
public string Tag { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -15,5 +15,6 @@ namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
public int AddedAt { get; set; }
|
||||
public int UpdatedAt { get; set; }
|
||||
public List<PlexMediaResponse> Media { get; set; }
|
||||
public List<PlexGenreResponse> Genre { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
{
|
||||
public class PlexPartResponse
|
||||
{
|
||||
@@ -6,5 +8,6 @@
|
||||
public string Key { get; set; }
|
||||
public int Duration { get; set; }
|
||||
public string File { get; set; }
|
||||
public List<PlexStreamResponse> Stream { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
|
||||
public bool Owned { get; set; }
|
||||
public string Provides { get; set; }
|
||||
public bool HttpsRequired { get; set; }
|
||||
public List<PlexResourceConnection> Connections { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
{
|
||||
public class PlexStreamResponse
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int StreamType { get; set; }
|
||||
public bool Anamorphic { get; set; }
|
||||
public string PixelAspectRatio { get; set; }
|
||||
public string ScanType { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -32,12 +32,6 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
tokens => tokens.Map(kvp => new PlexUserAuthToken(kvp.Key, kvp.Value)).ToList(),
|
||||
() => new List<PlexUserAuthToken>()));
|
||||
|
||||
public Task<List<PlexServerAuthToken>> GetServerAuthTokens() =>
|
||||
ReadSecrets().Map(
|
||||
s => Optional(s.ServerAuthTokens).Match(
|
||||
tokens => tokens.Map(kvp => new PlexServerAuthToken(kvp.Key, kvp.Value)).ToList(),
|
||||
() => new List<PlexServerAuthToken>()));
|
||||
|
||||
public Task<Option<PlexServerAuthToken>> GetServerAuthToken(string clientIdentifier) =>
|
||||
ReadSecrets().Map(
|
||||
s => Optional(s.ServerAuthTokens.SingleOrDefault(kvp => kvp.Key == clientIdentifier))
|
||||
@@ -61,6 +55,9 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
return SaveSecrets(secrets);
|
||||
});
|
||||
|
||||
public Task<Unit> DeleteAll() =>
|
||||
ReadSecrets().Bind(secrets => SaveSecrets(new PlexSecrets { ClientIdentifier = secrets.ClientIdentifier }));
|
||||
|
||||
private static Task<PlexSecrets> ReadSecrets() =>
|
||||
File.ReadAllTextAsync(FileSystemLayout.PlexSecretsPath)
|
||||
.Map(JsonConvert.DeserializeObject<PlexSecrets>)
|
||||
|
||||
@@ -60,6 +60,27 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Either<BaseError, MediaVersion>> GetStatistics(
|
||||
PlexMovie movie,
|
||||
PlexConnection connection,
|
||||
PlexServerAuthToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
IPlexServerApi service = RestService.For<IPlexServerApi>(connection.Uri);
|
||||
return await service.GetMetadata(movie.Key.Split("/").Last(), token.AuthToken)
|
||||
.Map(
|
||||
r => r.MediaContainer.Metadata.Filter(m => m.Media.Count > 0 && m.Media[0].Part.Count > 0)
|
||||
.HeadOrNone())
|
||||
.BindT(ProjectToMediaVersion)
|
||||
.Map(o => o.ToEither<BaseError>("Unable to locate metadata"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BaseError.New(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static Option<PlexLibrary> Project(PlexLibraryResponse response) =>
|
||||
response.Type switch
|
||||
{
|
||||
@@ -99,7 +120,8 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
Year = response.Year,
|
||||
Tagline = response.Tagline,
|
||||
DateAdded = dateAdded,
|
||||
DateUpdated = lastWriteTime
|
||||
DateUpdated = lastWriteTime,
|
||||
Genres = response.Genre.Map(g => new Genre { Name = g.Tag }).ToList()
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(response.Thumb))
|
||||
@@ -117,6 +139,21 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
metadata.Artwork.Add(artwork);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(response.Art))
|
||||
{
|
||||
var path = $"plex/{mediaSourceId}{response.Art}";
|
||||
var artwork = new Artwork
|
||||
{
|
||||
ArtworkKind = ArtworkKind.FanArt,
|
||||
Path = path,
|
||||
DateAdded = dateAdded,
|
||||
DateUpdated = lastWriteTime
|
||||
};
|
||||
|
||||
metadata.Artwork ??= new List<Artwork>();
|
||||
metadata.Artwork.Add(artwork);
|
||||
}
|
||||
|
||||
var version = new MediaVersion
|
||||
{
|
||||
Name = "Main",
|
||||
@@ -126,7 +163,8 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
AudioCodec = media.AudioCodec,
|
||||
VideoCodec = media.VideoCodec,
|
||||
VideoProfile = media.VideoProfile,
|
||||
SampleAspectRatio = ConvertToSAR(media.AspectRatio),
|
||||
// specifically omit sample aspect ratio
|
||||
DateUpdated = lastWriteTime,
|
||||
MediaFiles = new List<MediaFile>
|
||||
{
|
||||
new PlexMediaFile
|
||||
@@ -148,8 +186,21 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
return movie;
|
||||
}
|
||||
|
||||
private static string ConvertToSAR(double aspectRatio) => "1:1";
|
||||
// TODO: fix this with more detailed stats pull from plex for each item
|
||||
// Math.Abs(aspectRatio - 1) < 0.01 ? "1:1" : $"{(int) (aspectRatio * 100)}:100";
|
||||
private Option<MediaVersion> ProjectToMediaVersion(PlexMetadataResponse response)
|
||||
{
|
||||
Option<PlexStreamResponse> maybeStream =
|
||||
response.Media.Head().Part.Head().Stream.Find(s => s.StreamType == 1);
|
||||
return maybeStream.Map(
|
||||
stream => new MediaVersion
|
||||
{
|
||||
SampleAspectRatio = stream.PixelAspectRatio,
|
||||
VideoScanKind = stream.ScanType switch
|
||||
{
|
||||
"interlaced" => VideoScanKind.Interlaced,
|
||||
"progressive" => VideoScanKind.Progressive,
|
||||
_ => VideoScanKind.Unknown
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,22 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
string clientIdentifier = await _plexSecretStore.GetClientIdentifier();
|
||||
foreach (PlexUserAuthToken token in await _plexSecretStore.GetUserAuthTokens())
|
||||
{
|
||||
List<PlexResource> resources = await _plexTvApi.GetResources(clientIdentifier, token.AuthToken);
|
||||
IEnumerable<PlexMediaSource> sources = resources
|
||||
List<PlexResource> httpResources = await _plexTvApi.GetResources(
|
||||
0,
|
||||
clientIdentifier,
|
||||
token.AuthToken);
|
||||
|
||||
List<PlexResource> httpsResources = await _plexTvApi.GetResources(
|
||||
1,
|
||||
clientIdentifier,
|
||||
token.AuthToken);
|
||||
|
||||
|
||||
var allResources = httpResources.Filter(resource => resource.HttpsRequired == false)
|
||||
.Append(httpsResources.Filter(resource => resource.HttpsRequired))
|
||||
.ToList();
|
||||
|
||||
IEnumerable<PlexMediaSource> sources = allResources
|
||||
.Filter(r => r.Provides.Split(",").Any(p => p == "server"))
|
||||
.Filter(r => r.Owned) // TODO: maybe support non-owned servers in the future
|
||||
.Map(
|
||||
@@ -46,13 +60,16 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
resource.AccessToken);
|
||||
|
||||
_plexSecretStore.UpsertServerAuthToken(serverAuthToken);
|
||||
List<PlexResourceConnection> sortedConnections = resource.HttpsRequired
|
||||
? resource.Connections
|
||||
: resource.Connections.OrderBy(c => c.Local ? 0 : 1).ToList();
|
||||
|
||||
var source = new PlexMediaSource
|
||||
{
|
||||
ServerName = resource.Name,
|
||||
ProductVersion = resource.ProductVersion,
|
||||
ClientIdentifier = resource.ClientIdentifier,
|
||||
Connections = resource.Connections
|
||||
Connections = sortedConnections
|
||||
.Map(c => new PlexConnection { Uri = c.Uri }).ToList()
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user