merge latest develop (#230)
* sync guids/provider ids (#227) * sync guids from plex * cleanup * sync local guids * sync jellyfin and emby guids * add episodes to search index (#228) * sync episode directors and writers * display episode writers and directors * remove missing episodes from search index * show episodes in search results * fix emby and jellyfin episode updates * fix updating plex episodes * don't delete channel logos on startup * add episodes page; fix adding episodes to collection * cleanup * multi-part episode grouping fixes (#229)
This commit is contained in:
@@ -4,15 +4,16 @@ using Refit;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Plex
|
||||
{
|
||||
[Headers("Accept: application/json")]
|
||||
public interface IPlexServerApi
|
||||
{
|
||||
[Get("/library/sections")]
|
||||
[Headers("Accept: application/json")]
|
||||
public Task<PlexMediaContainerResponse<PlexMediaContainerDirectoryContent<PlexLibraryResponse>>> GetLibraries(
|
||||
[Query] [AliasAs("X-Plex-Token")]
|
||||
string token);
|
||||
|
||||
[Get("/library/sections/{key}/all")]
|
||||
[Headers("Accept: application/json")]
|
||||
public Task<PlexMediaContainerResponse<PlexMediaContainerMetadataContent<PlexMetadataResponse>>>
|
||||
GetLibrarySectionContents(
|
||||
string key,
|
||||
@@ -20,15 +21,33 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
string token);
|
||||
|
||||
[Get("/library/metadata/{key}")]
|
||||
public Task<PlexMediaContainerResponse<PlexMediaContainerMetadataContent<PlexMetadataResponse>>>
|
||||
GetMetadata(
|
||||
[Headers("Accept: text/xml")]
|
||||
public Task<PlexXmlVideoMetadataResponseContainer>
|
||||
GetVideoMetadata(
|
||||
string key,
|
||||
[Query] [AliasAs("X-Plex-Token")]
|
||||
string token);
|
||||
|
||||
[Get("/library/metadata/{key}")]
|
||||
[Headers("Accept: text/xml")]
|
||||
public Task<PlexXmlDirectoryMetadataResponseContainer>
|
||||
GetDirectoryMetadata(
|
||||
string key,
|
||||
[Query] [AliasAs("X-Plex-Token")]
|
||||
string token);
|
||||
|
||||
[Get("/library/metadata/{key}/children")]
|
||||
public Task<PlexMediaContainerResponse<PlexMediaContainerMetadataContent<PlexMetadataResponse>>>
|
||||
GetChildren(
|
||||
[Headers("Accept: text/xml")]
|
||||
public Task<PlexXmlSeasonsMetadataResponseContainer>
|
||||
GetShowChildren(
|
||||
string key,
|
||||
[Query] [AliasAs("X-Plex-Token")]
|
||||
string token);
|
||||
|
||||
[Get("/library/metadata/{key}/children")]
|
||||
[Headers("Accept: text/xml")]
|
||||
public Task<PlexXmlEpisodesMetadataResponseContainer>
|
||||
GetSeasonChildren(
|
||||
string key,
|
||||
[Query] [AliasAs("X-Plex-Token")]
|
||||
string token);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
{
|
||||
public class PlexDirectorResponse
|
||||
{
|
||||
[XmlAttribute("tag")]
|
||||
public string Tag { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
{
|
||||
public class PlexGenreResponse
|
||||
{
|
||||
[XmlAttribute("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[XmlAttribute("filter")]
|
||||
public string Filter { get; set; }
|
||||
|
||||
[XmlAttribute("tag")]
|
||||
public string Tag { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
{
|
||||
public class PlexGuidResponse
|
||||
{
|
||||
[XmlAttribute("id")]
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
{
|
||||
@@ -16,4 +17,32 @@ namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
{
|
||||
public List<T> Metadata { get; set; }
|
||||
}
|
||||
|
||||
[XmlRoot("MediaContainer", Namespace = null)]
|
||||
public class PlexXmlVideoMetadataResponseContainer
|
||||
{
|
||||
[XmlElement("Video")]
|
||||
public PlexXmlMetadataResponse Metadata { get; set; }
|
||||
}
|
||||
|
||||
[XmlRoot("MediaContainer", Namespace = null)]
|
||||
public class PlexXmlDirectoryMetadataResponseContainer
|
||||
{
|
||||
[XmlElement("Directory")]
|
||||
public PlexXmlMetadataResponse Metadata { get; set; }
|
||||
}
|
||||
|
||||
[XmlRoot("MediaContainer", Namespace = null)]
|
||||
public class PlexXmlSeasonsMetadataResponseContainer
|
||||
{
|
||||
[XmlElement("Directory")]
|
||||
public List<PlexXmlMetadataResponse> Metadata { get; set; }
|
||||
}
|
||||
|
||||
[XmlRoot("MediaContainer", Namespace = null)]
|
||||
public class PlexXmlEpisodesMetadataResponseContainer
|
||||
{
|
||||
[XmlElement("Video")]
|
||||
public List<PlexXmlMetadataResponse> Metadata { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,50 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
{
|
||||
public class PlexMediaResponse
|
||||
public class PlexMediaResponse<T>
|
||||
{
|
||||
[XmlAttribute("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[XmlAttribute("duration")]
|
||||
public int Duration { get; set; }
|
||||
|
||||
[XmlAttribute("bitrate")]
|
||||
public int Bitrate { get; set; }
|
||||
|
||||
[XmlAttribute("width")]
|
||||
public int Width { get; set; }
|
||||
|
||||
[XmlAttribute("height")]
|
||||
public int Height { get; set; }
|
||||
|
||||
[XmlAttribute("aspectRatio")]
|
||||
public double AspectRatio { get; set; }
|
||||
|
||||
[XmlAttribute("audioChannels")]
|
||||
public int AudioChannels { get; set; }
|
||||
|
||||
[XmlAttribute("audioCodec")]
|
||||
public string AudioCodec { get; set; }
|
||||
|
||||
[XmlAttribute("videoCodec")]
|
||||
public string VideoCodec { get; set; }
|
||||
|
||||
[XmlAttribute("videoResulution")]
|
||||
public string VideoResolution { get; set; }
|
||||
|
||||
[XmlAttribute("videoProfile")]
|
||||
public string VideoProfile { get; set; }
|
||||
|
||||
[XmlAttribute("container")]
|
||||
public string Container { get; set; }
|
||||
|
||||
[XmlAttribute("videoFrameRate")]
|
||||
public string VideoFrameRate { get; set; }
|
||||
public List<PlexPartResponse> Part { get; set; }
|
||||
|
||||
[XmlElement("Part")]
|
||||
public List<T> Part { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,74 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
{
|
||||
public class PlexMetadataResponse
|
||||
{
|
||||
[XmlAttribute("key")]
|
||||
public string Key { get; set; }
|
||||
|
||||
[XmlAttribute("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[XmlAttribute("contentRating")]
|
||||
public string ContentRating { get; set; }
|
||||
|
||||
[XmlAttribute("summary")]
|
||||
public string Summary { get; set; }
|
||||
|
||||
[XmlAttribute("year")]
|
||||
public int Year { get; set; }
|
||||
|
||||
[XmlAttribute("tagline")]
|
||||
public string Tagline { get; set; }
|
||||
|
||||
[XmlAttribute("thumb")]
|
||||
public string Thumb { get; set; }
|
||||
|
||||
[XmlAttribute("art")]
|
||||
public string Art { get; set; }
|
||||
|
||||
[XmlAttribute("originallyAvailableAt")]
|
||||
public string OriginallyAvailableAt { get; set; }
|
||||
|
||||
[XmlAttribute("addedAt")]
|
||||
public long AddedAt { get; set; }
|
||||
|
||||
[XmlAttribute("updatedAt")]
|
||||
public long UpdatedAt { get; set; }
|
||||
|
||||
[XmlAttribute("index")]
|
||||
public int Index { get; set; }
|
||||
|
||||
[XmlAttribute("studio")]
|
||||
public string Studio { get; set; }
|
||||
public List<PlexMediaResponse> Media { get; set; }
|
||||
|
||||
[XmlAttribute("rating")]
|
||||
public double Rating { get; set; }
|
||||
|
||||
[XmlAttribute("audienceRating")]
|
||||
public double AudienceRating { get; set; }
|
||||
|
||||
[XmlAttribute("audienceRatingImage")]
|
||||
public string AudienceRatingImage { get; set; }
|
||||
|
||||
[XmlAttribute("ratingImage")]
|
||||
public string RatingImage { get; set; }
|
||||
|
||||
[XmlIgnore]
|
||||
public virtual List<PlexMediaResponse<PlexPartResponse>> Media { get; set; }
|
||||
|
||||
[XmlElement("Genre")]
|
||||
public List<PlexGenreResponse> Genre { get; set; }
|
||||
|
||||
[XmlElement("Role")]
|
||||
public List<PlexRoleResponse> Role { get; set; }
|
||||
|
||||
[XmlElement("Director")]
|
||||
public List<PlexDirectorResponse> Director { get; set; }
|
||||
|
||||
[XmlElement("Writer")]
|
||||
public List<PlexWriterResponse> Writer { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
{
|
||||
public class PlexPartResponse
|
||||
{
|
||||
@@ -8,6 +6,5 @@ namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
public string Key { get; set; }
|
||||
public int Duration { get; set; }
|
||||
public string File { get; set; }
|
||||
public List<PlexStreamResponse> Stream { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
{
|
||||
public class PlexRoleResponse
|
||||
{
|
||||
[XmlAttribute("tag")]
|
||||
public string Tag { get; set; }
|
||||
|
||||
[XmlAttribute("role")]
|
||||
public string Role { get; set; }
|
||||
|
||||
[XmlAttribute("thumb")]
|
||||
public string Thumb { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,43 @@
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
{
|
||||
public class PlexStreamResponse
|
||||
{
|
||||
[XmlAttribute("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[XmlAttribute("index")]
|
||||
public int Index { get; set; }
|
||||
|
||||
[XmlAttribute("default")]
|
||||
public bool Default { get; set; }
|
||||
|
||||
[XmlAttribute("forced")]
|
||||
public bool Forced { get; set; }
|
||||
|
||||
[XmlAttribute("languageCode")]
|
||||
public string LanguageCode { get; set; }
|
||||
|
||||
[XmlAttribute("streamType")]
|
||||
public int StreamType { get; set; }
|
||||
|
||||
[XmlAttribute("codec")]
|
||||
public string Codec { get; set; }
|
||||
|
||||
[XmlAttribute("profile")]
|
||||
public string Profile { get; set; }
|
||||
|
||||
[XmlAttribute("channels")]
|
||||
public int Channels { get; set; }
|
||||
|
||||
[XmlAttribute("anamorphic")]
|
||||
public bool Anamorphic { get; set; }
|
||||
|
||||
[XmlAttribute("pixelAspectRatio")]
|
||||
public string PixelAspectRatio { get; set; }
|
||||
|
||||
[XmlAttribute("scanType")]
|
||||
public string ScanType { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
{
|
||||
public class PlexWriterResponse
|
||||
{
|
||||
[XmlAttribute("tag")]
|
||||
public string Tag { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
{
|
||||
public class PlexXmlMetadataResponse : PlexMetadataResponse
|
||||
{
|
||||
[XmlAttribute("guid")]
|
||||
public string PlexGuid { get; set; }
|
||||
|
||||
[XmlElement("Media")]
|
||||
public new List<PlexMediaResponse<PlexXmlPartResponse>> Media { get; set; }
|
||||
|
||||
[XmlElement("Guid")]
|
||||
public List<PlexGuidResponse> Guid { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
{
|
||||
public class PlexXmlPartResponse
|
||||
{
|
||||
[XmlAttribute("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[XmlAttribute("key")]
|
||||
public string Key { get; set; }
|
||||
|
||||
[XmlAttribute("duration")]
|
||||
public int Duration { get; set; }
|
||||
|
||||
[XmlAttribute("file")]
|
||||
public string File { get; set; }
|
||||
|
||||
[XmlElement("Stream")]
|
||||
public List<PlexStreamResponse> Stream { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Serialization;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
@@ -87,9 +88,9 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
{
|
||||
try
|
||||
{
|
||||
IPlexServerApi service = RestService.For<IPlexServerApi>(connection.Uri);
|
||||
return await service.GetChildren(show.Key.Split("/").Reverse().Skip(1).Head(), token.AuthToken)
|
||||
.Map(r => r.MediaContainer.Metadata)
|
||||
IPlexServerApi service = XmlServiceFor(connection.Uri);
|
||||
return await service.GetShowChildren(show.Key.Split("/").Reverse().Skip(1).Head(), token.AuthToken)
|
||||
.Map(r => r.Metadata)
|
||||
.Map(list => list.Map(metadata => ProjectToSeason(metadata, library.MediaSourceId)).ToList());
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -106,9 +107,9 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
{
|
||||
try
|
||||
{
|
||||
IPlexServerApi service = RestService.For<IPlexServerApi>(connection.Uri);
|
||||
return await service.GetChildren(season.Key.Split("/").Reverse().Skip(1).Head(), token.AuthToken)
|
||||
.Map(r => r.MediaContainer.Metadata.Filter(m => m.Media.Count > 0 && m.Media[0].Part.Count > 0))
|
||||
IPlexServerApi service = XmlServiceFor(connection.Uri);
|
||||
return await service.GetSeasonChildren(season.Key.Split("/").Reverse().Skip(1).Head(), token.AuthToken)
|
||||
.Map(r => r.Metadata.Filter(m => m.Media.Count > 0 && m.Media[0].Part.Count > 0))
|
||||
.Map(list => list.Map(metadata => ProjectToEpisode(metadata, library.MediaSourceId)).ToList());
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -125,12 +126,13 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
{
|
||||
try
|
||||
{
|
||||
IPlexServerApi service = RestService.For<IPlexServerApi>(connection.Uri);
|
||||
return await service.GetMetadata(key, token.AuthToken)
|
||||
IPlexServerApi service = XmlServiceFor(connection.Uri);
|
||||
return await service.GetVideoMetadata(key, token.AuthToken)
|
||||
.Map(Optional)
|
||||
.Map(
|
||||
r => r.MediaContainer.Metadata.Filter(m => m.Media.Count > 0 && m.Media[0].Part.Count > 0)
|
||||
r => r.Filter(m => m.Metadata.Media.Count > 0 && m.Metadata.Media[0].Part.Count > 0)
|
||||
.HeadOrNone())
|
||||
.MapT(response => ProjectToMovieMetadata(response, library.MediaSourceId))
|
||||
.MapT(response => ProjectToMovieMetadata(response.Metadata, library.MediaSourceId))
|
||||
.Map(o => o.ToEither<BaseError>("Unable to locate metadata"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -147,10 +149,10 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
{
|
||||
try
|
||||
{
|
||||
IPlexServerApi service = RestService.For<IPlexServerApi>(connection.Uri);
|
||||
return await service.GetMetadata(key, token.AuthToken)
|
||||
.Map(r => r.MediaContainer.Metadata.HeadOrNone())
|
||||
.MapT(response => ProjectToShowMetadata(response, library.MediaSourceId))
|
||||
IPlexServerApi service = XmlServiceFor(connection.Uri);
|
||||
return await service.GetDirectoryMetadata(key, token.AuthToken)
|
||||
.Map(Optional)
|
||||
.MapT(response => ProjectToShowMetadata(response.Metadata, library.MediaSourceId))
|
||||
.Map(o => o.ToEither<BaseError>("Unable to locate metadata"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -159,20 +161,30 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Either<BaseError, MediaVersion>> GetStatistics(
|
||||
public async Task<Either<BaseError, Tuple<MovieMetadata, MediaVersion>>> GetMovieMetadataAndStatistics(
|
||||
PlexLibrary library,
|
||||
string key,
|
||||
PlexConnection connection,
|
||||
PlexServerAuthToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
IPlexServerApi service = RestService.For<IPlexServerApi>(connection.Uri);
|
||||
return await service.GetMetadata(key, token.AuthToken)
|
||||
IPlexServerApi service = XmlServiceFor(connection.Uri);
|
||||
Option<PlexXmlVideoMetadataResponseContainer> maybeResponse = await service
|
||||
.GetVideoMetadata(key, token.AuthToken)
|
||||
.Map(Optional)
|
||||
.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"));
|
||||
r => r.Filter(m => m.Metadata.Media.Count > 0 && m.Metadata.Media[0].Part.Count > 0)
|
||||
.HeadOrNone());
|
||||
return maybeResponse.Match(
|
||||
response =>
|
||||
{
|
||||
Option<MediaVersion> maybeVersion = ProjectToMediaVersion(response.Metadata);
|
||||
return maybeVersion.Match<Either<BaseError, Tuple<MovieMetadata, MediaVersion>>>(
|
||||
version => Tuple(ProjectToMovieMetadata(response.Metadata, library.MediaSourceId), version),
|
||||
() => BaseError.New("Unable to locate metadata"));
|
||||
},
|
||||
() => BaseError.New("Unable to locate metadata"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -180,6 +192,57 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Either<BaseError, Tuple<EpisodeMetadata, MediaVersion>>> GetEpisodeMetadataAndStatistics(
|
||||
PlexLibrary library,
|
||||
string key,
|
||||
PlexConnection connection,
|
||||
PlexServerAuthToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
IPlexServerApi service = XmlServiceFor(connection.Uri);
|
||||
Option<PlexXmlVideoMetadataResponseContainer> maybeResponse = await service
|
||||
.GetVideoMetadata(key, token.AuthToken)
|
||||
.Map(Optional)
|
||||
.Map(
|
||||
r => r.Filter(m => m.Metadata.Media.Count > 0 && m.Metadata.Media[0].Part.Count > 0)
|
||||
.HeadOrNone());
|
||||
return maybeResponse.Match(
|
||||
response =>
|
||||
{
|
||||
Option<MediaVersion> maybeVersion = ProjectToMediaVersion(response.Metadata);
|
||||
return maybeVersion.Match<Either<BaseError, Tuple<EpisodeMetadata, MediaVersion>>>(
|
||||
version => Tuple(
|
||||
ProjectToEpisodeMetadata(response.Metadata, library.MediaSourceId),
|
||||
version),
|
||||
() => BaseError.New("Unable to locate metadata"));
|
||||
},
|
||||
() => BaseError.New("Unable to locate metadata"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BaseError.New(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private static IPlexServerApi XmlServiceFor(string uri)
|
||||
{
|
||||
var overrides = new XmlAttributeOverrides();
|
||||
var attrs = new XmlAttributes { XmlIgnore = true };
|
||||
overrides.Add(typeof(PlexMetadataResponse), "Media", attrs);
|
||||
|
||||
return RestService.For<IPlexServerApi>(
|
||||
uri,
|
||||
new RefitSettings
|
||||
{
|
||||
ContentSerializer = new XmlContentSerializer(
|
||||
new XmlContentSerializerSettings
|
||||
{
|
||||
XmlAttributeOverrides = overrides
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
private static Option<PlexLibrary> Project(PlexLibraryResponse response) =>
|
||||
response.Type switch
|
||||
{
|
||||
@@ -205,7 +268,7 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
|
||||
private PlexMovie ProjectToMovie(PlexMetadataResponse response, int mediaSourceId)
|
||||
{
|
||||
PlexMediaResponse media = response.Media.Head();
|
||||
PlexMediaResponse<PlexPartResponse> media = response.Media.Head();
|
||||
PlexPartResponse part = media.Part.Head();
|
||||
DateTime dateAdded = DateTimeOffset.FromUnixTimeSeconds(response.AddedAt).DateTime;
|
||||
DateTime lastWriteTime = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime;
|
||||
@@ -268,6 +331,23 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
Writers = Optional(response.Writer).Flatten().Map(w => new Writer { Name = w.Tag }).ToList()
|
||||
};
|
||||
|
||||
if (response is PlexXmlMetadataResponse xml)
|
||||
{
|
||||
metadata.Guids = Optional(xml.Guid).Flatten().Map(g => new MetadataGuid { Guid = g.Id }).ToList();
|
||||
if (!string.IsNullOrWhiteSpace(xml.PlexGuid))
|
||||
{
|
||||
string normalized = NormalizeGuid(xml.PlexGuid);
|
||||
if (!string.IsNullOrWhiteSpace(normalized) && metadata.Guids.All(g => g.Guid != normalized))
|
||||
{
|
||||
metadata.Guids.Add(new MetadataGuid { Guid = normalized });
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
metadata.Guids = new List<MetadataGuid>();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(response.Studio))
|
||||
{
|
||||
metadata.Studios.Add(new Studio { Name = response.Studio });
|
||||
@@ -311,7 +391,7 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
return metadata;
|
||||
}
|
||||
|
||||
private Option<MediaVersion> ProjectToMediaVersion(PlexMetadataResponse response)
|
||||
private Option<MediaVersion> ProjectToMediaVersion(PlexXmlMetadataResponse response)
|
||||
{
|
||||
List<PlexStreamResponse> streams = response.Media.Head().Part.Head().Stream;
|
||||
DateTime dateUpdated = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime;
|
||||
@@ -418,6 +498,23 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
.ToList()
|
||||
};
|
||||
|
||||
if (response is PlexXmlMetadataResponse xml)
|
||||
{
|
||||
metadata.Guids = Optional(xml.Guid).Flatten().Map(g => new MetadataGuid { Guid = g.Id }).ToList();
|
||||
if (!string.IsNullOrWhiteSpace(xml.PlexGuid))
|
||||
{
|
||||
string normalized = NormalizeGuid(xml.PlexGuid);
|
||||
if (!string.IsNullOrWhiteSpace(normalized) && metadata.Guids.All(g => g.Guid != normalized))
|
||||
{
|
||||
metadata.Guids.Add(new MetadataGuid { Guid = normalized });
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
metadata.Guids = new List<MetadataGuid>();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(response.Studio))
|
||||
{
|
||||
metadata.Studios.Add(new Studio { Name = response.Studio });
|
||||
@@ -461,7 +558,7 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
return metadata;
|
||||
}
|
||||
|
||||
private PlexSeason ProjectToSeason(PlexMetadataResponse response, int mediaSourceId)
|
||||
private PlexSeason ProjectToSeason(PlexXmlMetadataResponse response, int mediaSourceId)
|
||||
{
|
||||
DateTime dateAdded = DateTimeOffset.FromUnixTimeSeconds(response.AddedAt).DateTime;
|
||||
DateTime lastWriteTime = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime;
|
||||
@@ -476,6 +573,16 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
DateUpdated = lastWriteTime
|
||||
};
|
||||
|
||||
metadata.Guids = Optional(response.Guid).Flatten().Map(g => new MetadataGuid { Guid = g.Id }).ToList();
|
||||
if (!string.IsNullOrWhiteSpace(response.PlexGuid))
|
||||
{
|
||||
string normalized = NormalizeGuid(response.PlexGuid);
|
||||
if (!string.IsNullOrWhiteSpace(normalized) && metadata.Guids.All(g => g.Guid != normalized))
|
||||
{
|
||||
metadata.Guids.Add(new MetadataGuid { Guid = normalized });
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(response.Thumb))
|
||||
{
|
||||
var path = $"plex/{mediaSourceId}{response.Thumb}";
|
||||
@@ -516,47 +623,14 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
return season;
|
||||
}
|
||||
|
||||
private PlexEpisode ProjectToEpisode(PlexMetadataResponse response, int mediaSourceId)
|
||||
private PlexEpisode ProjectToEpisode(PlexXmlMetadataResponse response, int mediaSourceId)
|
||||
{
|
||||
PlexMediaResponse media = response.Media.Head();
|
||||
PlexPartResponse part = media.Part.Head();
|
||||
PlexMediaResponse<PlexXmlPartResponse> media = response.Media.Head();
|
||||
PlexXmlPartResponse part = media.Part.Head();
|
||||
DateTime dateAdded = DateTimeOffset.FromUnixTimeSeconds(response.AddedAt).DateTime;
|
||||
DateTime lastWriteTime = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime;
|
||||
|
||||
var metadata = new EpisodeMetadata
|
||||
{
|
||||
MetadataKind = MetadataKind.External,
|
||||
Title = response.Title,
|
||||
SortTitle = _fallbackMetadataProvider.GetSortTitle(response.Title),
|
||||
Plot = response.Summary,
|
||||
Year = response.Year,
|
||||
Tagline = response.Tagline,
|
||||
DateAdded = dateAdded,
|
||||
DateUpdated = lastWriteTime,
|
||||
Actors = Optional(response.Role).Flatten().Map(r => ProjectToModel(r, dateAdded, lastWriteTime))
|
||||
.ToList()
|
||||
};
|
||||
|
||||
if (DateTime.TryParse(response.OriginallyAvailableAt, out DateTime releaseDate))
|
||||
{
|
||||
metadata.ReleaseDate = releaseDate;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(response.Thumb))
|
||||
{
|
||||
var path = $"plex/{mediaSourceId}{response.Thumb}";
|
||||
var artwork = new Artwork
|
||||
{
|
||||
ArtworkKind = ArtworkKind.Thumbnail,
|
||||
Path = path,
|
||||
DateAdded = dateAdded,
|
||||
DateUpdated = lastWriteTime
|
||||
};
|
||||
|
||||
metadata.Artwork ??= new List<Artwork>();
|
||||
metadata.Artwork.Add(artwork);
|
||||
}
|
||||
|
||||
EpisodeMetadata metadata = ProjectToEpisodeMetadata(response, mediaSourceId);
|
||||
var version = new MediaVersion
|
||||
{
|
||||
Name = "Main",
|
||||
@@ -589,6 +663,67 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
return episode;
|
||||
}
|
||||
|
||||
private EpisodeMetadata ProjectToEpisodeMetadata(PlexMetadataResponse response, int mediaSourceId)
|
||||
{
|
||||
DateTime dateAdded = DateTimeOffset.FromUnixTimeSeconds(response.AddedAt).DateTime;
|
||||
DateTime lastWriteTime = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime;
|
||||
|
||||
var metadata = new EpisodeMetadata
|
||||
{
|
||||
MetadataKind = MetadataKind.External,
|
||||
Title = response.Title,
|
||||
SortTitle = _fallbackMetadataProvider.GetSortTitle(response.Title),
|
||||
Plot = response.Summary,
|
||||
Year = response.Year,
|
||||
Tagline = response.Tagline,
|
||||
DateAdded = dateAdded,
|
||||
DateUpdated = lastWriteTime,
|
||||
Actors = Optional(response.Role).Flatten().Map(r => ProjectToModel(r, dateAdded, lastWriteTime))
|
||||
.ToList(),
|
||||
Directors = Optional(response.Director).Flatten().Map(d => new Director { Name = d.Tag }).ToList(),
|
||||
Writers = Optional(response.Writer).Flatten().Map(w => new Writer { Name = w.Tag }).ToList()
|
||||
};
|
||||
|
||||
if (response is PlexXmlMetadataResponse xml)
|
||||
{
|
||||
metadata.Guids = Optional(xml.Guid).Flatten().Map(g => new MetadataGuid { Guid = g.Id }).ToList();
|
||||
if (!string.IsNullOrWhiteSpace(xml.PlexGuid))
|
||||
{
|
||||
string normalized = NormalizeGuid(xml.PlexGuid);
|
||||
if (!string.IsNullOrWhiteSpace(normalized) && metadata.Guids.All(g => g.Guid != normalized))
|
||||
{
|
||||
metadata.Guids.Add(new MetadataGuid { Guid = normalized });
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
metadata.Guids = new List<MetadataGuid>();
|
||||
}
|
||||
|
||||
if (DateTime.TryParse(response.OriginallyAvailableAt, out DateTime releaseDate))
|
||||
{
|
||||
metadata.ReleaseDate = releaseDate;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(response.Thumb))
|
||||
{
|
||||
var path = $"plex/{mediaSourceId}{response.Thumb}";
|
||||
var artwork = new Artwork
|
||||
{
|
||||
ArtworkKind = ArtworkKind.Thumbnail,
|
||||
Path = path,
|
||||
DateAdded = dateAdded,
|
||||
DateUpdated = lastWriteTime
|
||||
};
|
||||
|
||||
metadata.Artwork ??= new List<Artwork>();
|
||||
metadata.Artwork.Add(artwork);
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
private Actor ProjectToModel(PlexRoleResponse role, DateTime dateAdded, DateTime lastWriteTime)
|
||||
{
|
||||
var actor = new Actor { Name = role.Tag, Role = role.Role };
|
||||
@@ -605,5 +740,32 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
|
||||
return actor;
|
||||
}
|
||||
|
||||
private string NormalizeGuid(string guid)
|
||||
{
|
||||
if (guid.StartsWith("plex://show") ||
|
||||
guid.StartsWith("plex://season") ||
|
||||
guid.StartsWith("plex://episode") ||
|
||||
guid.StartsWith("plex://movie"))
|
||||
{
|
||||
return guid;
|
||||
}
|
||||
|
||||
if (guid.StartsWith("com.plexapp.agents.thetvdb"))
|
||||
{
|
||||
string strip1 = guid.Replace("com.plexapp.agents.thetvdb://", string.Empty);
|
||||
string strip2 = strip1.Split("?").Head();
|
||||
return $"tvdb://{strip2}";
|
||||
}
|
||||
|
||||
if (guid.StartsWith("com.plexapp.agents.themoviedb"))
|
||||
{
|
||||
string strip1 = guid.Replace("com.plexapp.agents.themoviedb://", string.Empty);
|
||||
string strip2 = strip1.Split("?").Head();
|
||||
return $"tmdb://{strip2}";
|
||||
}
|
||||
|
||||
throw new NotSupportedException(guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user